Meteor 1.3 and React Composition with Reactive Data
Note: This blog post is a republish from my original post over at the Spacedojo Medium publication.
Unless you’ve been living under a rock you know that mixins are dead when writing React components with ES2015 classes. The new way to handle data is to use composition/higher-order components. The problem is that Meteor’s official solution for reactive data is using a mixin. I recently attended Josh Owens’ live Mastering Meteor class and he went over Meteor 1.3 and loading data in React. Let’s check out the previous way of handling reactive data, and then compare two new ways, I learned about in class, of handling data with composition.
ReactMeteorData Mixin and getMeteorData()
Unfortunately, Meteor’s official solution for handling reactive data in a React component uses a mixin, called ReactMeteorData. While mixins can work in ES2015 classes by using an experimental ES7 feature called decorators or with a special packages like ReactMixin, it’s best to know that they will not stick around forever. By using the ReactMeteorData, you have access to a method called getMeteorData() that houses your reactive data. Data returned by getMeteorData is usable in the render() method by accessing this.data. Here’s a simple example:
TodoList.jsx;
TodoList = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
var handle = Meteor.subscribe("todoList", this.props.id);
return {
loading: !handle.ready(),
todoList: TodoLists.findOne(this.props.id),
todoListTasks: Tasks.find({ listId: this.props.id }).fetch(),
};
},
render() {
const { loading, todoList, todoListTasks } = this.data;
if (loading) {
return <LoadingSpinner />;
}
return (
<div>
<h1>{todoList.name}</h1>
<ul>
{todoListTasks &&
todoListTasks.map((task, index) => {
return <li key={index}>{task}</li>;
})}
</ul>
</div>
);
},
});
Higher-Order Components, Composition, Containers, UI components
The answer to replacing mixins is by building two pieces of code: a container and a UI component (sometimes called a dumb component). The role of the container is to fetch data and pass that data down as props to the UI component. The role of the UI component is to display that data. Pretty simple, huh? Of course, there is always more to worry about like showing a loading indicator, showing an error if it occurs, and re-fetching and passing the new data to the UI component. This is where a few packages step in to save the day.
React Komposer: Composing React Components
React Komposer is an NPM package created by Kadira. It helps users create containers with less boilerplate by handling loading indicators and cleaning up resources. React-Komposer’s goal is to also works with a wide range of other technologies besides Meteor’s Tracker like Promises, Rx.JS, and Observables. Below is the same example we saw previously, but instituted with composition using React Komposer.
TodoListContainer.js
import { composeWithTracker } from 'react-composer';
import TodoList from '../components/TodoList.jsx';
function composer(props, onData) {
const subscription = Meteor.subscribe('todoList', props.id);
if (subscription.ready()) {
const todoList = TodoLists.findOne({ props.id });
const todoListTasks = Tasks.find({ listId: props.id }).fetch();
onData(null, { todoList, todoListTasks });
};
};
export default composeWithTracker(composer)(TodoList);
Above is a container called TodoListContainer.js which subscribes to a publication, and when is ready calls other queries. You take the data you query and pass it to the onData method which will pass it off as props to the component below.
TodoList.jsx;
export const TodoList = ({ todoList, todoListTasks }) => (
<div>
<h1>{todoList.name}</h1>
<ul>
{todoListTasks.map((task, index) => {
return <li key={index}>{task}</li>;
})}
</ul>
</div>
);
Pretty easy. Even better than that, React Komposer handles displaying the loading indicator until onData method is called and it will display any errors we pass it automatically for us! If you want to change the loading indicator or error display, it’s effortless.
Now on to TrackerReact, a more straightforward approach. Unfortunately, TrackerReact does not help with some of the repetitive aspects of using containers, like stopping subscriptions, displaying loading indicators, or handling passed errors.
TrackerReact: No-Config Reactive React Components With Meteor
TrackerReact has been out a while, but has seen a major overhaul recently for Meteor 1.3. The documentation states that it is well suited for small to mid-sized applications that have simple features. TrackerReact is Meteor focused and allows a React component to become reactive by wrapping the component with Tracker. The benefit of wrapping the component with Tracker is developers can use any of Meteor’s reactive data sources like collections, methods, and sessions from anywhere in the component. TrackerReact feels more like a natural ‘Meteor way’ to handle things inside of React. TrackerReact can be used in composition, as a mixin, or as a decorator.
Below is our same example, using TrackerReact:
TodoListContainer.jsx
import TrackerReact from 'meteor/ultimatejs:tracker-react';
class App extends TrackerReact(React.Component) {
constructor() {
super();
this.state = {
subscription: {
todoList: Meteor.subscribe('todoList', this.props.id)
}
}
}
componentWillUnmount() {
this.state.subscription.todoList.stop();
}
todoList() {
return TodoLists.findOne(this.props.id).fetch();
},
todoListTasks() {
return Tasks.find({ listId: this.props.id }).fetch();
},
render() {
return (
<TodoList
todoList={ this.todoList }
todoListTasks={ this.todoListTasks }
/>
);
}
};
TodoList.jsx;
export const TodoList = ({ todoList, todoListTasks }) => (
<div>
<h1>{todoList.name}</h1>
<ul>
{todoListTasks.map((task, index) => {
return <li key={index}>{task}</li>;
})}
</ul>
</div>
);
For more information on how to use TrackerReact as a mixin or decorator, check out the GitHub README.
Decorators are experimental and mixins are meeting their end, so we have to change how we write React components. Understanding containers and how they interact with components is not complicated, but we have several tools to make building them easier.
If you want a more opinionated solution, stick with React Komposer. But if you are just beginning to shift away from mixins, I would take a good look TrackerReact. Both offer pros and cons, such as Komposer’s automatic loading indicator or TrackerReact’s ability to use any of Meteor’s reactive sources.
Now get back to work, and start composing!