Meteor + React Design Pattern: Mixins v. Composable Components

Sam Corcos
3 min readAug 16, 2015

--

The current means to integrate Meteor data into your React components is to use the ReactMeteorData mixin. This works well and is intuitive, but there are two potential issues: 1) Mixins are Dead. Long Live Composition and 2) views and data are jumbled up in the same component.

To solve the first issue, we need to start moving away from mixins. To solve the second, we need another design pattern. One potential solution to this problem is to use composable components (discussed here). I’ve also found that something like this is already common practice, called container components.

It’s good practice to keep your view components pure and manage your data outside of your view components, which should only be used for rendering. It’s already common practice in React to wrap your view components in a higher-order component that manages your data.

The basic pattern is the following (in pseudocode), with components and sub components:

<MainView>  <Data
<SubView1 />
/>
<Data
<SubView2 />
/>
</MainView>

Composable Components

We’ll call the component MeteorData. Because the entire composable component is a reusable higher-order function, you don’t need use the mixin anymore. A fully functional proof of concept can be found here.

Our new MeteorData component will look like this (explained below the code):

MeteorData = React.createClass({
componentWillMount() {
this.c = Tracker.autorun(() => {
const sub = this.props.subscribe()
const state = this.props.fetch()
state.loading = !sub.ready()
this.setState(state)
})
},
componentWillUnmount() {
this.c.stop()
},
render() {
return this.state ? this.props.render(this.state) : false
}
})

Within componentWillMount(), we’re running an autorun that manages the state and watches for changes. We’re also passing subscribe(), fetch(), and render() as props. State has two properties: the data you fetch and loading, determined by !sub.ready().

When the component unmounts — componentWillUnmount() — we’re going to stop the computation to avoid memory leaks.

Then we render passing in our state, which as you will recall contains the data and the loading status. It’s worth noting that Tracker.autorun is asynchronous, so it’s possible that componentWillMount could return before Tracker.autorun is called. This is why we have the ternary conditional in our render function.

The next thing we need to do is create the data. If you’re following along, you can paste this code into your jsx file to populate a collection and publish it to the client.

We then need to create the view component, which we will call Component. In the code below, you’ll see that we’re using this.props.loading to make sure the data is loaded before we try to render it. We’re also taking the data from only the first result of the data array. If there were more than one item, you would have to render it separately.

Component = React.createClass({
render() {
if (this.props.loading) { return <h1>App is loading</h1> }
return (
<div>
This is where the data goes: {this.props.data[0].info}
</div>
)
}
})

In order to fully understand how this works, we need to see an example component:

<MeteorData
subscribe = { () => {
return Meteor.subscribe('alldata') }}
fetch = { () => {
return {data: MyData.find().fetch()} }}
render = { ({loading, data}) => {
return <Component loading={loading} data={data} />}}
/>

In this component, we’re subscribing to the messages publication, fetching the data we want from that publication, then rendering the MessageList subcomponent (which we won’t bother defining for this example), which takes in loading and messages (if this is confusing, see destructing assignments) from the MeteorData state.

Phew! And now we have our data and our view in separate components.

Additional

You can do the same thing using the ReactMeteorData mixin in your MeteorData if you’d prefer. This might not be a bad decision, as it’s likely Meteor Development Group (MDG) has put a lot more into this effort than just a Tracker.autorun:

MeteorData = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
const sub = this.props.subscribe()
const data = this.props.fetch()
data.loading = !sub.ready()
return data;
},
render() {
return this.props.render(this.data)
}
})

There is an ongoing discussion in Meteor about this pattern. If you have some thoughts, chime in!

Sam Corcos is the lead developer and co-founder of Sightline Maps, the most intuitive platform for 3D printing topographical maps, as well as LearnPhoenix.io, an advanced tutorial site for building scaleable production apps with Phoenix and React.

--

--

Sam Corcos

Software developer, founder, author - CarDash - Learn Phoenix - SightlineMaps.com