What exactly does mobx-react-lite's "useLocalStore" hook do, and why is it (only sometimes) needed? - mobx

Within the mobx-react documentation there are variations in how stores are created. For example, on the React Context page:
In the first code sample, the store is instantiated with useLocalStore:
const store = useLocalStore(createStore)
In the second code sample, the stores are initiated by directly "newing" the stores":
counterStore: new CounterStore(),
themeStore: new ThemeStore(),
By inference, the first is a "local" store (and thus needs useLocalStore), and the second is a "global" store and thus doesn't. However, it is not clear why this is, and what the subsequent differnce in behaviour is.
Why is useLocalStore not needed in second example, and what difference does this make to the behavior of the stores and mobx within React?
Thanks for any input

OK, I found the answer. useLocalStore turns a javascript literal into a store with observable properties. This is not needed if a store is created from a class object with observable attributes.
Thanks to #freddyc for the answer

useLocalStore has been deprecated in favor of useLocalObservable, see here.

Links from the question are no longer valid, mobx-react docs are now here:
https://mobx.js.org/react-integration.html
useLocalObservable(initializer, annotations)
is just a shorthand for:
useState(() => observable(initializer(), annotations, {autoBind: true}))[0]
useLocalStore is almost the same, but it was deprecated because it wasn't as practical.
Why is this needed?
useState() is the main way to store a variable in React.
observable({...}) is the main way to create observable objects in Mobx.
And when using Mobx+React you'll often need the useState + observable combo, so it is nice to have a shorthand.
When is it not needed?
If you are using a class to create your store then you don't need the observable wrapper and you can simply do:
const myStore = useState(() => new MyStore())[0];
If you are using React class components you can save the store on a class field.
class MyComponent
{
myStore = new MyStore();
render() {...}
}
Bonus tip:
Why do we use useState for memorizing values instead of useMemo?
useMemo is meant to be used more like a cache for performance optimization, rather than for storing app state.

Related

Vue.js global data access with v-model

I am working on a web app where users can work on a project. The structure of the app is as follows:
Component A (app)
Component B1-Bn (header, footer, main window etc., children of A)
Component C1 (Input area; with inputs for the user to work on the project, child of main window)
Component C2 (Output area; canvas which shows the result based on inputs from C1. In the future also a "graphical" input area that syncs with C1. Child of main window)
Component D1-Dn (Single parts of the input area like tables, advanced input components etc. Child of C1)
Now the project that the user is working on consists of an object stored in Component A. Component Dn needs to write to the object in Component A and also C2 in the future.
I can't get the v-model on input components Dn to work. I tried to pass the data from A down to C1 via props / v-bind and then in Dn, I v-model the prop from C1 (which originates from A) to the input-field. I also tried to use the sync modifier without sucess.
I seem to have a lack of understanding of the vue logic. I come from a desktop background where you just define the scope of variables.
I also found that other vue apprentices have the same understanding problem but somehow the answers I found where not sufficient.
I want a "global" variable that can be edited by every component and is linked to elements in the DOM. What would be the best way to achieve this?
Declare your variable at data when creating Vue Object in your root component (Component A) like
var app = new Vue({
data: function(){
return {
showSetting: {}
}
},
})
Now you can access this showSetting variable in any component like
app.showSetting;
//change it in any component
app.showSetting = {a:1,b:2};
//or append new value to object
Object.assign({d:3},app.showSetting);
Thanks for the answers so far. I guess both of them work. I found another solution because now I fully understand how data is passed in vue:Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child component will affect parent state. I will pass all data as arrays in the future, as I only want references. The only question that remains is why the programmer is not allowed to define by himself whether the data is passed by reference or not...
Source: Vue.js Guide

How can I observe any change (property added, removed or changed) in a mobx observable map?

class FilterCriteria {
#observable filter = new Map();
}
let criteria = new FilterCriteria ();
// setting up a reaction when something in the filter changes
// (property added, removed, or changed)
reaction(()=>criteria.filter, data => console.log(data.toJSON()));
criteria.filter.set('name', 'John'); // setting a new property.
I would expect the above code to print out { 'name': 'John' }, but it seems that the reaction is not running.
I suspect that I set up the reaction in the wrong way. I want to react whenever a new key is added, an existing key is removed or a key value is changed. I don't know the keys or values at compile time.
How am I supposed to do that?
UPDATE
I changed my code to
class FilterCriteria {
#observable filter = new Map();
#computed get json(){ return this.filter.toJSON(); }
}
...
reaction(()=>criteria.json, data => console.log(data));
and now it seems to work properly. The reaction sideffect is executed whenever I add, remove or change a value in the Map.
So the question is why the reaction did execute in the second but not in the first example?
UPDATE 2
I changed my code again for a second time. I reverted to almost the first version but this time instead of reacting on criteria.filter and logging data.toJSON(), i react on criteria.filter.toJSON() and I log data (toJSON is moved from the sideffect to the value being watched). This time the reaction runs normally.
class FilterCriteria {
#observable filter = new Map();
}
reaction(()=>criteria.filter.toJSON(), data => console.log(data));
Again, I don't understand why. If criteria.filter is not an observable in itself then how does the watched expression is reevaluated when something inside criteria.filter is changed?
UPDATE 4 (hope the final one) SOLUTION
According to MobX documentation, mobx reacts to any existing observable property that is read during the execution of a tracked function.
reaction side-effect executes when the observable property changes. In my example, when reacting to criteria.filter , the observable property that is read here is filter, but the filter itself never changes. It is the same map always. It is the properties of filter that change. So the reaction is never run for criteria.filter.
But when I react on criteria.filter.toJSON() or mobx.toJS(criteria.filter), the reaction is executed correctly.
So why is that? criteria.filter doesn't change, and toJSON is not an observable property. It is a function. same for mobx.toJS. It seems no properties are read here. But this is not correct. As the documentation states (but not so emphatically), the properties of criteria.filter are indeed read when toJSON or mobx.toJS is executed, because both functions create a deep clone of the map (thus iterating over every property).
Now, in the beginning, the Map did not contain any property. So how is it that newly added properties are tracked, since they did not exist (to be read) when tracking begun? This is a map's feature. Maps provide observability for not yet existing properties too.
In MobX 5 you can track not existing properties of observable objects (not class instances) too, provided that they were instatiated with observable or observable.object. Class instances don't support this.
In mobx you have two options when you want to observe changes to something that is observable. reaction and observe. Reaction allows you to specify when you want some function to be called when a specific aspect of the observable changes. This could be changes to an array length, keys, properties, really anything. observe will trigger some function any time that the observable has changed.
I suspect the reason that your reaction hasn't been triggered is because of the first function. () => criteria.filter. This will not be triggered when a key is added/removed or a value changed. Instead, it will be triggered when filter actually changes. And since filter is really a reference to the Map, it will never change, even when the Map itself changes.
Here are some examples to illustrate my point:
If you want to trigger a reaction when a key has been added or removed, you may want your function to be:
() => criteria.filter.keys()
The result of this function will be different when a key has been added or removed. Similarly, if you want to trigger a reaction for when a value has been modified, something like this should work:
() => criteria.filter.values()
So some combination of those two should be what you need to listen to changes to keys/values. Alternatively, you could use observe, which will trigger on every change and require you to check what has changed to ensure that your specific conditions have been met to warrant calling a function (ie. key/value change)
UPDATE: Here is an example that illustrates the problem
#observable map = new Map();
Lets say that the value of map in memory is 5. So when you check map === map, it is equivalent to 5 === 5 and will evaluate to true.
Now, looking at the first code snippet you posted:
reaction(() => map, data => console.log(map.toJSON()));
Every time you add/remove a key or change a value, that first function will run. And the result will be 5, since that is what we said the value in memory is for this example. It will say: the old value is 5, and the new value is 5, so there is no change. Therefore, the reaction will not run the second function.
Now the second snippet:
reaction(() => map.toJSON(), data => console.log(data));
At first the result of the function will be: {} because the Map is empty. Now lets add a key:
map.set(1, 'some value');
Now, the result of the first function will be:
{"1": "some value"}
Clearly, this value is different than {}, so something has changed, and the second function of the reaction is called.

How to properly select from multiple Redux-slices in mapStateToProps?

My Redux store is normalized, i.e. it's quite flat and each entity type has it's own slice.
Here is a simplified example of my Redux store:
drawings
1: {name:'D1', thumbnailId: 33}
2: {name:'D2', thumbnailId: 34}
thumbnails
33: {filePath: 'path to local file'}
34: {filePath: null (i.e. needs to be downloaded)}
The listview that shows the drawings whith respective thumbnail needs to be re-rendered when:
Changes in drawings-slice occurs, i.e. new, removed or updated drawings
Changes in any of the referenced thumbnails occurs, i.e. thumbnail 34 eventually gets downloaded (download is handled async by a Redux-Saga)
My current mapStateToProps is obviously flawed, as it does excessive selections from the Redux store. This happens before the actual view gets hold of its new props, so I cannot control this from shouldComponentUpdate.
There are several places in my app where I need a better solution for this.
Here is my flawed mapStateToProps (I'm using Immutable.js):
(state, ownProps) => {
const drawings = selectProjectDrawings(state, ownProps.projectId).map(drawing => {
const thumbnailFileGuid = drawing.get('thumbnailFileGuid');
if (!thumbnailFileGuid) return drawing;
const filePath = selectFile(state, thumbnailFileGuid).get(THUMBNAIL_FILE_PATH_FIELD);
return filePath ? drawing.set('_thumbnailFilePath', filePath) : drawing;
});
return {
drawings: drawings
};
}
Edit:
A really bad thing with my current solution is that I'm creating new drawing object by augmenting them with _thumbnailPath. This means that I cannot compare the object references in shouldComponentUpdate, as the reference is always altered.
Being able to compare object references is one of the main argument for not mutating the objects, but I have thrown away this opportunity with my flawed solution.
I would suggest placing this logic behind a selector. reselect is a selector memoization library which means that it will do a deep compare of the values for you and if the returned object and all the properties within stay the same, (including deep tree like structures) then it will return the original object.
React then will notice this is the same object instance (not a new one) and not re-render. It is OK if you recreate the drawing object within the selector, since you are not capturing this in any persistent state.
Side note: As your app grows, you will notice that all connected components will get mapStateToProps called even if it has nothing to do with it, therefore using a memoized selector really helps here. Otherwise your componentShouldUpdate gets complex real quick.

Mobx Autorun in practice

I'm trying to get Mobx's autorun to work correctly.
My use case is I have one model that I like to serialize (or dehydrate) when it is changed and add that information to another model's data. This brings me rudimentary time travel of model states. Both are observables.
Edit: Idea in model separation is that one is app's data model and other should be completely separate library that I could use from the app. I need to track changes in the app regularly, but show UI for the state tool on the same page.
Now, autorun seems to make its own inferences of what I'm actually tracking. When I moved the model instance inside observing model's instantiation, autorun wasn't called anymore when changes happened. When model instance was created on the module top level, it worked as I expected. This was when I only changed one property of observing model (the one that gets changed by every autorun call). When I tried changing two things at once in the observing model, autorun was now called for these changes also, leading to a unending cycle (which Mobx caught).
I'd like to know how to express what I'm tracking with autorun function be more explicit, or wether there are other ways to keep track of model changes and update other model when anything happens.
Edit with code example.
This is what I did (greatly simplified):
class DataModel {
#observable one_state = null;
}
class StateStore {
#observable states = [];
}
let data = new DataModel();
let store = new StateStore();
autorun(() => {
store.states.push(data.one_state);
console.log("new data", toJSON(store.states));
});
data.one_state = "change 1";
data.one_state = "change 2";
And this creates circular dependency because autorun gets called for both original data model change and the resulting store change, whilst I'm only interested in tracking changes to the former.
Edit with working result:
class DataModel {
#observable one_state = null;
}
class StateStore {
#observable states = asFlat([]);
}
let data = new DataModel();
let store = new StateStore();
autorun(() => {
store.states.push(data.one_state);
});
data.one_state = "change 1";
data.one_state = "change 2";
As per #mweststrate answer, using asFlat with store's states variable and removing the logging from autorun broke the problem cycle.
It is a bit tough to answer this question without any real code. Could you share some code? But note that MobX works best if you make a small mind shift: instead of imperatively saying "if X happens Y should be changed" it is better to say "Y can be derived from X". If you think along those lines, MobX will really start to shine.
So instead of having two observable models, I think one of them should be a derivation of the other (by using computed indeed). Does that make sense? Otherwise, feel free to elaborate on your question a bit more :)
Edit:
Ok thanks for the code. You should remove the log statement to avoid it from looping; Currently you log the states model, so each time it changes, the autorun will run, adding the first item (again!), changing the stateModel etc...
Secondly I'm not sure whether the states list should be observable, but at least its contents should not be observable (since it is a snapshot and the data per state should not change). To express that, you can use the asFlat modifier, which indicats that the states collection should only be shallowly observable: #observable states = asFlat([]).
Does that answer your question?

DoJo get/set overriding possible

I don't know much about Dojo but is the following possible:
I assume it has a getter/setter for access to its datastore, is it possible to override this code.
For example:
In the dojo store i have 'Name: #Joe'
is it possible to check the get to:
get()
if name.firstChar = '#' then just
return 'Joe'
and:
set(var)
if name.firstChar = '#' then set to #+var
Is this sort of thing possible? or will i needs a wrapper API?
You can get the best doc from http://docs.dojocampus.org/dojo/data/api/Read
First, for getting the data from a store you have to use
getValue(item, "key")
I believe you can solve the problem the following way. It assumes you are using a ItemFileReadStore, you may use another one, just replace it.
dojo.require("dojo.data.ItemFileReadStore");
dojo.declare("MyStore", dojo.data.ItemFileReadStore, {
getValue:function(item, key){
var ret = this.inherited(arguments);
return ret.charAt(0)=="#" ? ret.substr(1) : ret;
}
})
And then just use "MyStore" instead of ItemFileReadStore (or whatever store you are using).
I just hacked out the code, didn't try it, but it should very well show the solution.
Good luck
Yes, I believe so. I think what you'll want to do is read this here and determine how, if it will work:
The following statement leads me to believe the answer is yes:
...
By requiring access to go through
store functions, the store can hide
the internal structure of the item.
This allows the item to remain in a
format that is most efficient for
representing the datatype for a
particular situation. For example, the
items could be XML DOM elements and,
in that case, the store would access
the values using DOM APIs when
store.getValue() is called.
As a second example, the item might be
a simple JavaScript structure and the
store can then access the values
through normal JavaScript accessor
notation. From the end-users
perspective, the access is exactly the
same: store.getValue(item,
"attribute"). This provides a
consistent look and feel to accessing
a variety of data types. This also
provides efficiency in accessing items
by reducing item load times by
avoiding conversion to a defined
internal format that all stores would
have to use.
...
Going through store accessor function
provides the possibility of
lazy-loading in of values as well as
lazy reference resolution.
http://www.dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/what-dojo-data/dojo-data-design
I'd love to give you an example but I think it's going to take a lot more investigation.