Reputation: 351
I have three components; Form, Preview & AppStore. Clicking a button in Form adds an item to the store. This seems to work fine except that the list in the Preview component isn't updating/re-rendering when the store changes even though it has an @observer decorator. What am I missing?
Form has a button and handler function that adds an item to the store:
@inject('AppStore')
@observer class Form extends React.Component{
handleAddItem= (item) =>{
const {AppStore} = this.props;
AppStore.addItem(item);
console.log(AppStore.current.items)
}
render(){
return(
<button onClick={() => this.handleAddItem('Another Item')}>Add Item</button>
)}
}
Preview maps through the items (I'm using a drag and drop hoc so my code might look a bit odd)
@inject('AppStore')
@observer class Preview extends React.Component
...
return(
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
</ul>)
...
return <SortableList items={AppStore.current.items} onSortEnd={this.onSortEnd} />;
Here is store:
import { observable, action, computed } from "mobx";
class AppStore {
@observable other = {name: '', desc:'', items: [ 'item 1', 'item 2', 'item 3'], id:''}
@observable current = {name: '', desc:'', items: [ 'item 1', 'item 2', 'item 3'], id:''}
@action addItem = (item) => {
this.current.items.push(item)
}
}
const store = new AppStore();
export default store;
Upvotes: 5
Views: 8680
Reputation: 2974
Try to replace your action to be:
@action addItem = (item) => {
this.current.items = this.current.items.concat([item]);
}
Here instead of using push
for mutating the property, use concat
which is used to merge two arrays and return a whole new array with a new reference that MobX can react to.
Upvotes: 0
Reputation: 5651
I'm fairly certain this is a case where MobX doesn't extend observability all the way down into your current.items
array.
Objects in MobX will extend observability when first constructed/initialized -- so current.items
is an observable property in the sense that if you changed it's value to some other primitive, your component would re-render.
For example:
current.items = 1; // changing it from your array to some totally new value
current.items = []; // this _might_ also work because it's a new array
Similarly, if AppStore had a top-level observable items
that you were changing, then calling items.push()
would also work.
class AppStore {
@observable items = [];
@action additem = (item) => {
this.items.push(item);
}
}
The problem in your case is that items
is buried one level deep inside an observable object -- so pushing items into the current.items
array isn't changing the value of the property in a way that MobX can detect.
It's admittedly very confusing, and the common MobX pitfalls are sometimes hard to understand.
See also this line in the Object documentation:
Only plain objects will be made observable. For non-plain objects it is considered the responsibility of the constructor to initialize the observable properties.
Upvotes: 3