Pass state to subscription


#1

I’m using the the Meteor React tutorials to learn how to use Meteor and React. I have completed the Todo tutorial. I wanted to modify it by using a drop down of categories that would be assigned to each task. Whenever the categories drop down changes, i set it’s value in state. I see that I am unable to reference the state in the container as to limit the published data to only those task with the category specified. How would I pass the state to the subscription so that it only return the tasks I have selected. I expect for it to return a new collection whenever the state changes. This is where I’m at: https://www.meteor.com/tutorials/react/publish-and-subscribe.

this is the code I want to use which doesn’t work, the state is not avail inside of the container:

In my App.jsx

export default createContainer(() => {
  Meteor.subscribe('tasks', this.state.category);

  return {
    tasks: Tasks.find({category: this.state.category}, { sort: { createdAt: -1 } }).fetch(),
    incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    currentUser: Meteor.user(),
  };
}, App);

In my task.js

export const Tasks = new Mongo.Collection('tasks');

if (Meteor.isServer) {
  // This code only runs on the server
  Meteor.publish('tasks', function tasksPublication(category) {
    return Tasks.find({category: category);
  });
}

#2

createContainer creates a new wrapping component. It has it’s own props and state separate from App. More importantly, this becomes a stateless functional component which does little more than hold data for you to consume. Thus it doesn’t have access to “this” because that function being an arrow function binds it’s own this which in this case would be global.

To accomplish what you want you’re going to have to get a bit more creative. There are a few routes you can go:

  1. Create your own higher order component to store the data which exposes functions to the child component to “set it’s state” and avoid using createContainer
  2. Create a function on your child component which will use Tracker autorun to call forceUpdate thus allowing you to reactively “observe” the collection without the higher order component.
  3. Use the old Meter Data mixin. There are a few ways to get mixin behavior on your class without going back in time to the old factory syntax.
  4. Use a global state engine (such as Flux or Meteor’s Session) to store that state and expose it in a global manner.

There are of course others, but these are the first options that came to mind. Mix and Match for your desired output.


#3

I would pass category as a property to the container:

// ListContainer.jsx
export default createContainer(({category}) => {
  Meteor.subscribe('tasks', category);
  (...)
```

so now you have a container that can be used like this: `<ListContainer categoryId={categoryId} />`

You can use this container in a "classic" ReactComponent that stores the selected `categoryId` in the state:

`````

(...)
render() {
   return <div>
         <select onChange={this.onCategoryChange.bind(this)} value={this.state.categoryId}>{this.renderCategoryOptions()}</select>
        <ListContainer categoryId={this.state.categoryId} />
   </div>
}
```

You could also "store" the `categoryId` as a pathParam in the url when the category is changed in the dropdown-menu. This way, your state (the selected categoryId) is persisted in the url and you would not need a "stateful" component like above:

````

FlowRouter.route('/taskList/:categoryId', {
    action({categoryId}) {
        mount(AppContainer, {categoryId});
    }
});

```

If you do not want to have the categoryId in the url you create another container / hoc that "injects" the categoryId from a global state (like from Session.get("categoryId") or another ReactiveDict)

Something like:

```
export default createContainer(() => {
  return {categoryId: Session.get("categoryId")}
}, ListContainer);
```

hope this helps!