Łukasz Ostrowski
Łukasz Ostrowski

Reputation: 1020

MobX state tree - array of models doesn't trigger update

I have following model:

 const AnotherModel = types.model({
    foo: types.string
 });

 export const SomeCollectionModel = types
     .model({
         data: types.array(AnotherModel),
     })
     .views((self) => ({
         getData: () => self.data,
     }))
     .actions((self) => ({
         fetchData: flow(function* fetchData() {
             try {
                 const data =
                     yield service.fetchData();
                 self.data.replace(
                     data.map((f) => AnotherModel.create(f)),
                     // Using plain json instead of create does the same
                 );
             } catch (err) {
                 console.error(err);
             }
         })
    }));

Then I have two React components, which I compose with inject and observer.

Parent one (view/page):

compose(
    inject((states) => ({
        fetchData: () =>
            states.myModel.fetchData(),
    })),
    observer,
)

Child one:

compose(
    inject(states => ({
        data: states.myModel.getData()
    })),
    observer
)

I have a re-rednering problem. Initially child component doesn't render anything. In the meantime data is fetched (action triggers itself). However data is not updated in child. componentWillReact doesnt trigger. After changing routes (rerender by router) view gets updated

Do you have any idea? I'm stuck for hours.

Upvotes: 3

Views: 6075

Answers (1)

Luis Herranz
Luis Herranz

Reputation: 386

  1. You don't need to create a view to access your raw data, you can use the prop directly:
inject(states => ({
  data: states.myModel.data
}))
  1. You don't need to use observer if you use inject. MyComponent will observe what's inside the inject just fine:
inject(states => ({
  data: states.myModel.data
}))(MyComponent)
  1. In this case, you are observing the array, which is a reference. If you update the items of the array, the array itself doesn't change so MyComponent is not re-rendered. You could observe the array length, that way if the length changes, MyComponent is re-rendered, but it's only useful when you add or remove items from the array:
inject(states => ({
  data: states.myModel.data,
  length: states.myModel.data.length
}))(MyComponent)
  1. Finally, if want to observe changes to the items of the array even if the length doesn't change, you need to observe the items internally:
inject(states => ({
  data: states.myModel.data,
  length: states.myModel.data.length
}))(MyComponent)

const MyComponent = ({ data }) => (
  <div>{data.map(item => (
    <MyDataItem item={item} />
  ))}
  </div>
)

inject((states, props) => ({
  foo: props.item.foo
}))(MyDataItem)

Upvotes: 4

Related Questions