George Bleasdale
George Bleasdale

Reputation: 351

Component doesn't re-render when store updates - Mobx

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

Answers (2)

Mohamed Ibrahim Elsayed
Mohamed Ibrahim Elsayed

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

arthurakay
arthurakay

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

Related Questions