MobX documentation suggests I should use observer
on all my components. However by using inject I get more fine grained control over what data causes a re-rendering of my components.
My understanding is I that with observer
, a change in all accessed observables in the last render will cause a re-render, even if the observable is nested deep in the data store, while inject
only re-renders when observables accessed in the injector function change.
For example:
class Store{
@observable data = {
nestedData: {
deepData: 'my_data'
}
}
}
const store = new Store();
... Assume the store is injected using <Provider /> component
// This will cause re-render when the data object changes
// for example: store.data = { new_data: 'new_data' }
@inject(stores => {
return { data: stores.dataStore.data };
})
class MyComponent extends Component { }
// This will re-render on change of the data object, but also
// on change of nestedData and deepData properties
@inject(stores => {
return { data: stores.dataStore.data };
})
@observer
class MyComponent extends Component { }
Could someone confirm my understanding of this?
In my opinion, it's better to use only inject
as it gives you more control, and can prevent unnecessary re-renders. If the data is deeply nested you could create a computed property that gets and prepares the data from the deep structure and then inject that property in the component.
Are there other benefits/drawbacks when using one over the other
I believe you are correct in your assessment. Let me try to rephrase for clarity:
@observer
tracks which observables are used by render
and automatically re-renders the component when one of these values changes.
We should note that @observable
values used by render
might be deeply nested within a given prop
, per your example:
class Store{
@observable data = {
nestedData: {
// changes to `deepData` would theoretically re-render any observer
deepData: 'my_data'
}
}
}
with observer, a change in all accessed observables in the last render will cause a re-render, even if the observable is nested deep in the data store
Bingo!
Although there's a quirk with observable
, as you'll see in a moment...
On the other hand you have @inject
which makes available to a component (via props
) specific data structures defined by a Provider
.
For example:
@inject('title')
class MyComponent extends React.Component {
render() {
return (<div>{this.props.title}</div>);
}
}
const Container = () => (
<Provider title="This value is passed as a prop using `inject`">
<MyComponent />
</Provider>
);
inject only re-renders when observables accessed in the injector function change.
Bingo!
inject
will only spawn a re-render if the prop
itself has recognized changes.
This is effectively the same issue with shouldComponentUpdate() and a deep-comparison of props
-- though observer
seems to be far more efficient than shouldComponentUpdate
.
In my opinion, it's better to use only inject as it gives you more control, and can prevent unnecessary re-renders.
I wouldn't necessarily go that far... it all depends on how you have your code structured.
If I modify your original example as so:
class Store{
@observable data = {
nestedData: {}
};
constructor() {
this.data.nestedData.deepData = 'my_data';
}
}
...the addition of deepData
won't actually get picked up as an observable change (i.e. re-render) because that property didn't exist when we originally tagged data
as an observable value. So that's one problem.
A different approach could be to do something like this:
class Person {
@observable name = 'John Doe';
}
class Store{
@observable data = null;
constructor() {
this.data = new Person();
}
}
This allows you to spread the observable values out across classes -- so you might still want to inject Store
into a component (to access Store.data
but ultimately any observable changes come from updating Person
.