score:1

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).


Related Query

More Query from same tag