Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react meteor data container doesn't update child when props change

Tags:

reactjs

meteor

I have been struggling with this issue for quite some time and have failed to find any answers.

I use react-meteor-data to manage my data with react in my meteor application. It is working fine when dealing with data for mongo but I can't make it reactive with props.

Here in App.js, I call my container which I want to be reactive and rerender when the state of App change.

<MyContainer someState={this.state.MyState} />

In MyContainer.js I have a createContainer from react-meteor-data

export default createContainer(params => {
   Meteor.subscribe('someCollection');

   return {
      someCollection: SomeCollection.find({}).fetch(),
      stateFromParent: params.someState
   };
}, MyContainer);

This worked fine when rendering the component for the first time, MyContainer correctly get MyState.

The thing is, when the MyState from App change, I can see in Chrome Dev React tool that it is indeed updated for the createContainer( ReactMeteorDataComponent has a prop with the right updated state) but the createContainer function is not run, thus the props do not update for MyContainer.

So the props are updated from ReactMeteorDataComponent but not for MyContainer who keeps indefinitely the data. It's like createContainer doesn't consider the update of its prop has a change and thus doesn't run its function.

I really think I'm missing something since that seems pretty basic stuff, thank you for your help.

like image 208
Sylvain Laugier Avatar asked Nov 07 '22 19:11

Sylvain Laugier


1 Answers

The OP did not mention how the state was changed, so the original example is incomplete. Therefore, I will try to explain the gist of how the container creation works, in hope that understanding it will be useful.


How does it work?

It uses meteor's Tracker to auto-update the wrapped component when its computation is invalidated (i.e, when one of the reactive data sources, such as reactive variables, subscription handles or fetched MiniMongo cursors, has a new value). To learn more about Tracker, consult the Tracker manual. This is an in-depth resource, and is not necessary to understand how the basics work.

It does so in a way that is different from the way you normally approach reactivity tracking in Meteor, since it also needs to re-run the computation whenever the container's props are changed.

The source code is not very long or complex and can be found on GitHub (currently here).

  Tracker.autorun((c) => {
    if (c.firstRun) {
      //...
        data = component.getMeteorData();

    } else {
      // Stop this computation instead of using the re-run.
      // We use a brand-new autorun for each call to getMeteorData
      // to capture dependencies on any reactive data sources that
      // are accessed.  The reason we can't use a single autorun
      // for the lifetime of the component is that Tracker only
      // re-runs autoruns at flush time, while we need to be able to
      // re-call getMeteorData synchronously whenever we want, e.g.
      // from componentWillUpdate.
      c.stop();
      // Calling forceUpdate() triggers componentWillUpdate which
      // recalculates getMeteorData() and re-renders the component.
      component.forceUpdate();
    }
  })

Whenever the computation is invalidated (and therefore rerun), it stops the computation and forces a re-render of the container, which will re-create a new computation and have the updated data.

The high-level container functionality is here (some parts were removed for brevity):

export const ReactMeteorData = {
  componentWillMount() {
    this.data = {};
    this._meteorDataManager = new MeteorDataManager(this); // (1)
    const newData = this._meteorDataManager.calculateData(); // (2)
    this._meteorDataManager.updateData(newData); // (3)
  },

  componentWillUpdate(nextProps, nextState) {
    // backup current state and props, assign next ones to components
    let newData = this._meteorDataManager.calculateData(); // (2)
    this._meteorDataManager.updateData(newData); // (3)
    // restore backed up data
  },

  componentWillUnmount() {
    this._meteorDataManager.dispose(); // (4)
  },
};

The main points are: - Before being mounted, a new data manager is created (1). It is in charge of running the computation and populating this.data according to data changes. - At first and whenever the component should update, the computation is run (2) and the data is updated (3). The update happens whenever the component receives new state or props (in this type of container, it should only be props), and, as we saw earlier, also when the Tracker computation is invalidated, due to the call to component.forceUpdate().

The wrapped component receives the parent's props, as well as the Tracker computation's data as props:

return <WrappedComponent {...this.props} {...this.data} />;

Any more points as to how it should be used?

The react-meteor-data has a short section in the meteor guide.

Generally, the simple example in the guide (as well as the OP's example) should work just fine, as long as the state is set appropriately, using setState() (see the "how does it work?" section above).

Also, there is no need to re-map the container state to props sent to the child, as they are passed along (unless there is a very good reason for doing so).

Do consider the point in the preventing re-renders section if you encounter any performance issues.

From the guide:

export default ListPageContainer = withTracker(({ id }) => {
  const handle = Meteor.subscribe('todos.inList', id);
  const loading = !handle.ready();
  const list = Lists.findOne(id);
  const listExists = !loading && !!list;
  return {
    loading,
    list,
    listExists,
    todos: listExists ? list.todos().fetch() : [],
  };
})(ListPage);

in this example, note that the container expects an id prop, and it will also be made available to the wrapped component, as well as loading, list, etc (which come from the container's computation in the example).

like image 199
MasterAM Avatar answered Nov 15 '22 04:11

MasterAM