Moving To Containers in Meteor (ES6) For React
By Vince
The current way to connect Meteor into your React App is to use ES6 based components. Meteor has a function called “createContainer” that will send Meteor data into your React component as props instead of “this.data” access. This can make it a bit more complicated if you need to change your subscriptions based on React’s state, but we will talk about the new pattern you will need to use. This is a better pattern long term as you can switch out the top level data injection and re-use all your display logic (say you want to swap out Meteor down the road, or use Redux).
Old: Mixin Based Meteor Style (one file with the component in it):
const App = React.createClass({
// This mixin makes the getMeteorData method work
mixins: [ReactMeteorData],
// Loads items from the collection and puts them on this.data.myData
getMeteorData() {
const handle = Meteor.subscribe('myDataSub');
return {
dataReady: handle.ready(),
myData: myDB.find({}).fetch(),
};
},
... other React methods, functions, and render()...
The Mixin allows you to have access to your data inside the component. While this is handy, the new format pulls this out and creates an outer “Container” component that feeds the data into your App via props. This is the React way of doing things. So with the ES6 style you will create two files, one the Meteor container, and one your component.
New ES6 Style, this is the outer “Container”:
import {createContainer} from 'meteor/react-meteor-data';
import App from './App';
export default AppContainer = createContainer((props) => {
const handle = Meteor.subscribe('myDataSub');
return {
dataReady: handle.ready(),
myData: handle.ready() ? myDB.find({}).fetch() : [],
};
}, App);
Child component, App.jsx:
class App extends React.Component {
//All your React methods, functions, render(), etc
//Access this.props.dataReady and this.props.myData
}
export default App;
So now wherever you need to call “App”, you instead will import and call “AppContainer”. “AppContainer” will then connect to your Meteor subscription and send down myData as “this.props.myData” inside of your App component. That’s really the main difference, no more “this.data” access, just do a find and replace of this.data with this.props inside App and your done. The main change is adding a new file/component and updating where you import them. Good practice is to call your outer component Componentname Container so you can quickly realize its a container which sends data down to children (replace Componentname with the name, such as App).
There are two main questions that pop up with this new format/way of doing things.
- Can I pass other props into AppContainer and do they pass down?
- Yes, if you are passing other props down (such as react-router does), those are available inside the AppContainer (see the (props) after createContainer), and those are also automatically passed down to your App. So any additional this.props are available and you don’t need to manually pass them inside your Meteor return statement.
- What happens if my Meteor subscription needs to change based on some state.
- This was an issue I ran into, we are going to need to break down the previous App component into three components. Let’s talk about this more.
Changing your Meteor subscription from child component state
What happens when your Meteor subscription changes based on some sort of user input inside your app? This is pretty common if you are changing results based on a user searching for something, passing down a specific blog post via the post ID, or sending down details on some other Mongo Document. So how can you do this? Well you need to split your component into three components (and three files to maintain clarity).
Here is the pattern: Parent Component -> Meteor Container -> Child Component.
Parent Component which holds the state, has the state setting functions, and sends the props down to your Meteor container:
import AppContainer from './AppContainer';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {subID: 1};
//setup our binding to this component/class, I like to do this up top for cleaner code
this.incrementSubID = this.incrementSubID.bind(this);
}
incrementSubID() {
//we are adding one to get the next subscription, fiction here, wouldn't really use this
this.setState({subID: this.state.subID + 1});
}
render() {
//here we are passing the subID down as a prop, and the incrementSubId handler which will pass through
//we are sending the function down as a prop, incrementSubId=this.incrementSubID, then you can call that directly
return <AppContainer subID={this.state.subID} incrementSubId=this.incrementSubID} />;
}
}
The Meteor Container which pulls out props and changes the subscription based on that:
import {createContainer} from 'meteor/react-meteor-data';
import App from './App';
export default AppContainer = createContainer((props) => {
//here we can pull out the props.subID and change our Meteor subscription based on it
//this is handled on the publication side of things
const handle = Meteor.subscribe('myDataSub', props.subID);
return {
dataReady: handle.ready(),
myData: handle.ready() ? myDB.find({}).fetch() : [],
};
}, App);
Then your child component which displays the results based on the previous props. This will also include the selector function (to change your state) which will be passed down from the parent component:
class App extends React.Component {
//All your other React methods, functions, etc
render(){
return <div>
<p>Your subscription ID is: {this.props.subID} </p>
<button onClick={this.props.incrementSubId}>Click Me To Increment</button>
</div>
}
}
export default App;
So that’s all there is to it, you need to setup a top-level parent which will hold all our state so the Meteor subscription can update.