Many data sources in Meteor are "reactive" — that is, they use Meteor's Tracker library to notify data consumers when something has changed. These data sources include the following:
Meteor.user() - the currently logged-in userMongo.Collection - a persistent collection that can be accessed from the clientReactiveVar - store a single value reactivelyThis mixin comes with the react meta-package; you can also include it directly with meteor add react-meteor-data.
In order to make it easy to use these data sources together with React components, we have created a React mixin called ReactMeteorData. Once you have added this mixin to your component, you can define a method called getMeteorData on your component.
Inside getMeteorData, you can access any Meteor reactive data source, as well as this.props and this.state. getMeteorData will reactively rerun when the accessed data changes. getMeteorData must return an object, and the properties of the object will be copied onto the component's this.data. To use the data, you access this.data from the render() method.
Subscriptions that you make from getMeteorData using Meteor.subscribe will be automatically maintained across reruns of getMeteorData, and cleaned up when the component unmounts. The arguments to a subscription can depend on this.props and this.state.
A simple component that just says hello to the currently logged in user:
var HelloUser = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
return {
currentUser: Meteor.user()
};
},
render() {
return <span>Hello {this.data.currentUser.username}!</span>;
}
});
A component that fetches some data based on an ID passed as a prop and passes it down to a child component:
var TodoListLoader = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
// This is the place to subscribe to any data you need
var handle = Meteor.subscribe("todoList", this.props.id);
return {
todoListLoading: ! handle.ready(), // Use handle to show loading state
todoList: TodoLists.findOne(this.props.id),
todoListTasks: Tasks.find({listId: this.props.id}).fetch()
};
},
render() {
// Show a loading indicator if data is not ready
if (this.data.todoListLoading) {
return <LoadingSpinner />;
}
// Render a component and pass down the loaded data
return (
<TodoList
list={this.data.todoList}
tasks={this.data.todoListTasks} />
);
}
});
React has a handy shouldComponentUpdate hook for preventing unnecessary rerenders. That works for props and state changes, but will not help you prevent a re-render when this.data is updated.
If you find that your component is re-rendering too often because of spurious changes in data, you can split it into two components - a wrapper component that just loads the data, and a child that actually renders the view. Then, you can pass this.data into the child through props, and use shouldComponentUpdate in the child to prevent unnecessary re-renders.
render() is not reactiveIf you access a Meteor reactive data source from your component's render method, the component will not automatically rerender when data changes. If you want your component to rerender with the most up-to-date data, access all reactive functions from inside the getMeteorData method.
React now supports defining components in the form of ES6 classes, but these classes do not support mixins. In some future version of React, mixins might not be the recommended way for shipping functionality to integrate into React components.
However, after some research and discussion with React developers from different companies, we have found that mixins are currently the best practice. Popular libraries, such as ReactRouter, are sticking with mixins until something better comes along.
Mixins are also the best way of polyfilling React's proposed standard pattern for getting reactive data into components. When the observe API is shipped in React, or when decorators or mixins are added to JavaScript classes, we will consider switching to those if they provide a better integration. We expect the community will also experiment with other integrations.
this.data instead of this.stateSome previous Meteor and React integrations put the data retrieved from Meteor collections on the state field of the component. While this does have some advantages, for example being able to use shouldComponentUpdate to block updates, we have decided to go with a different pattern for the officially supported integration. We didn't take this decision lightly; it is based on extensive research into the wider React ecosystem, how React is used with other libraries, and future React API discussions. Here are some of our findings:
data property. This property, and the ability to observe reactive data, might be built into React at some point. When it is, we want the migration path to be as short as possible.The component's this.data is initially populated from a componentDidMount callback.
When the component recieves new props or state, here's what React does normally:
componentWillReceiveProps(nextProps) (if there are new props)shouldComponentUpdate(nextProps, nextState) (and maybe stop the update)componentWillUpdate(nextProps, nextState)this.props = nextProps and this.state = nextStaterender()React's upcoming observe API adds a new step between steps 4 and 5 — after assigning this.props and this.state, and before calling render() — in which a method named observe() is called, and this.data is updated. To simulate this extra step, the mixin uses a componentWillUpdate callback (step 3) that calls getMeteorState while temporarily swapping in the new values for this.props and this.state, putting them right back when it's done.
Note that you can still use all the lifecycle callbacks, including shouldComponentUpdate, for their usual purpose. However, shouldComponentUpdate can only be used to stop updates caused by changes to props and state, not data.
Finally, if a Meteor reactive data source changes that was accessed from getMeteorData, the mixin calls forceUpdate() on the component, which triggers the update steps listed above, leading to getMeteorData being called again.