I have been encountering a bit of problem finding an efficient way to sync mobservable with changing props and wonder if there is a better way to do this.
class MyComponent {
componentWillMount() { this.update(); }
componentWillReceiveProps(nextProps) { this.update(nextProps); }
componentWillUnmount() { if (this.handler) { this.handler(); this.handler = null; } }
update(props) {
let {item} = (props || this.props);
if (this.item === item) return; // no change
this.item = item; !this.handler || this.handler();
this.handler = autorun(() => {
// watch for changes to observables on node and then fetch new data locally for this component
item.fetchData((err, data) => this.setState({data}); }
});
}
render() {
let {node} = this.props;
let {data} = this.state;
return <div>{item.name}: {data.something}</div>;
}
}
The sync boilerplate (and synchronization bugs) pattern seems to happen when you:
(1) are passing down subsections of your domain store to components in props (the item is changing dynamically in the props)
(2) when there is asynchronously fetched data (such as paged results) that are transient and localized to the component, eg. does not seem to like a good idea to store local paging data for a specific item in a global store (for example, if you need to display two items independently)
I'm trying to write small, modular, and testable components that exist in their own npm modules so decoupling logic from a global store would be preferable. Not sure if this means that I should create a local store per module in the component state and watch it?
I might be just having trouble making a leap to a fully reactive solution! Any recommendations?
First some general comments (which might be off, I don't know the specifics of your use case ;-))
I usually try to avoid reconciliation of components for _different_ items which would avoid the componentWillReceiveProps issue alltogether. To achieve this just pass a proper key to the react component that is based on the item (for example using item.name if that is unique, and _not_ the array index which is an React anti pattern). In general it is just easier if one component is always bound to the same item for its complete lifecycle.
Besides that, you could simplify the current setup a bit marking the item property as @observable. You can then start the autorun in your constructor and only need to dispose it in the componentWillUnmount (or use autorunUntil if you know what will cause your component to dismount).
So you could simplify your component probably to:
class MyComponent {
// I think item shouldn't even change in the lifecycle of the component,
// in that case @observable is not required
@observable item;
componentWillMount() {
this.handler = autorun(() => {
// watch for changes to observables on node and then fetch new data locally for this component
// note 1: if item can change, you might want to abort the old request
// note 2: react won't like it if fetchData returns after unmounting the component
item && item.fetchData((err, data) => this.setState({data}); }
});
}
componentWillUnmount() { if (this.handler) { this.handler(); this.handler = null; } }
render() {
let {node} = this.props;
let {data} = this.state;
return <div>{item.name}: {data.something}</div>;
}
}
Good idea! Assigning a key to ensure uniqueness is probably the easiest and best way to achieve this.
Then I can use my autorun decorator to manage the autorun handler lifecycle. Closing and will this example to the documentation...
I've tried the idea, but it is a little unsatisfactory because it requires the component user to know to add a key. I've been able to improve this idea and create a wrapper component that adds a key to ensure single use. It could probably be generalized more than for mobservable (for example, via a decorator) if it already hasn't...I've added a note to the Wiki for docs
Yeah, the key thing is annoying because you cannot abstract it away easily, yet part of the React game. Lot of React beginner tutorials also point out the importance of stable keys for component reconciliation.
Most helpful comment
First some general comments (which might be off, I don't know the specifics of your use case ;-))
I usually try to avoid reconciliation of components for _different_ items which would avoid the componentWillReceiveProps issue alltogether. To achieve this just pass a proper key to the react component that is based on the item (for example using
item.nameif that is unique, and _not_ the array index which is an React anti pattern). In general it is just easier if one component is always bound to the same item for its complete lifecycle.Besides that, you could simplify the current setup a bit marking the
itemproperty as@observable. You can then start theautorunin your constructor and only need to dispose it in thecomponentWillUnmount(or useautorunUntilif you know what will cause your component to dismount).So you could simplify your component probably to: