Updated February 11, 2016
Easily Connect React Native to a Meteor Server
Originally publish on medium.com.
This post is now outdated as the technology has changed. If you want to learn the latest please check out my profile or sign up for my email list, where I’ll send you the most up to date information about React Native + Meteor.
With Parse recently announcing that they will be shutting down, many companies are looking into the next steps to take. This shut down may lead many away from BaaS products and towards an in-house backend — such as Meteor.
Let’s walk through what it’s like to connect a React Native app to a Meteor app (serving as the backend). This tutorial assumes you already have React Native and Meteor installed and working on your system.
You can access the finished app (Meteor and React Native) on Github.
Create the Meteor App
meteor create meteor-app
Then we’re going to remove autopublish and insecure, generally a best practice.
cd meteor-app && meteor remove autopublish insecure
And add the Random package.
meteor add random
We’re also going to change the file structure up a bit. Go ahead and delete meteor-app.html, meteor-app.js, and meteor-app.css.
Then add the following files (and folders):
- /both/posts.js
- /client/home.html
- /client/home.js
- /client/index.html
- /server/app.js
We’ll go over what goes into each of those files in a moment.
Add Functionality to the Meteor App
This is going to be an extremely simple app. The point is just to show how to make the connection, subscribe, and make method calls. All we’re doing is creating a Posts collection, seeding it with some data, and setting up a publication. Then on the client we’re making a subscription and displaying a count of posts returned from the database. The user can then add or delete a post. That’s the functionality we’ll mimic on the React Native app. With that, here’s the code…
/both/posts.js
Posts = new Mongo.Collection('posts');
Meteor.methods({
addPost: function () {
Posts.insert({ title: 'Post ' + Random.id() });
},
deletePost: function () {
let post = Posts.findOne();
if (post) {
Posts.remove({ _id: post._id });
}
},
});
/server/app.js
Meteor.startup(function () {
if (Posts.find().count() === 0) {
for (i = 1; i <= 10; i++) {
Posts.insert({ title: 'Post ' + Random.id() });
}
}
});
Meteor.publish('posts', function () {
return Posts.find();
});
/client/home.html
<template name="home">
<h1>Post Count: {{count}}</h1>
<button id="increment">Increment</button>
<button id="decrement">Decrement</button>
</template>
/client/home.js
Template.home.onCreated(function () {
this.subscribe('posts');
});
Template.home.helpers({
count() {
return Posts.find().count();
},
});
Template.home.events({
'click #increment': function (e) {
e.preventDefault();
Meteor.call('addPost');
},
'click #decrement': function (e) {
e.preventDefault();
Meteor.call('deletePost');
},
});
/client/index.html
<head>
<title>meteor-app</title>
</head>
<body>
{{> home}}
</body>
You’ve now got a functioning Meteor app! Type meteor into your terminal window and try adding/removing posts.
Create the React Native App
In a new terminal window run
react-native init RNApp && cd RNApp
Setting up the React Native App
We’ll have just a few files to work within the React Native App — creating 2 and modifying 2.
app/index.js
import React, { View, Text, StyleSheet } from 'react-native';
import Button from './button';
export default React.createClass({
getInitialState() {
return {
connected: false,
posts: {},
};
},
handleIncrement() {
console.log('inc');
},
handleDecrement() {
console.log('dec');
},
render() {
let count = 10;
return (
<View style={styles.container}>
<View style={styles.center}>
<Text>Posts: {count}</Text>
<Button text="Increment" onPress={this.handleIncrement} />
<Button text="Decrement" onPress={this.handleDecrement} />
</View>
</View>
);
},
});
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#F5FCFF',
},
center: {
alignItems: 'center',
},
});
app/button.js
import React, { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
export default React.createClass({
render() {
let { text, onPress } = this.props;
return (
<TouchableOpacity style={styles.button} onPress={onPress}>
<Text>{text}</Text>
</TouchableOpacity>
);
},
});
const styles = StyleSheet.create({
button: {
flex: 1,
backgroundColor: '#eee',
paddingHorizontal: 20,
paddingVertical: 10,
marginVertical: 10,
},
});
index.ios.js
import React, { AppRegistry, Component } from 'react-native';
import App from './app';
class RNApp extends Component {
render() {
return <App />;
}
}
AppRegistry.registerComponent('RNApp', () => RNApp);
index.android.js
import React, { AppRegistry, Component } from 'react-native';
import App from './app';
class RNApp extends Component {
render() {
return <App />;
}
}
AppRegistry.registerComponent('RNApp', () => RNApp);
You should now have a working React Native app! Though it won’t do much…
Connecting React Native App to Meteor
Now the fun part. Lets actually get the two apps talking to each other. We’ll be using the ddp-client package, simply because I’m most familiar with it.
I’m going to be using version 0.1.1 because that’s what the docs on the master branch’s README reference.
Install it via:
npm install ddp-client@0.1.1 --save
First we’ll import the ddp client library and instantiate it. We’re just going to use the default options since this is only development. The ddb-client README outlines other options you can use in the ddp configuration.
app/index.js
import React, { View, Text, StyleSheet } from 'react-native';
import Button from './button';
import DDPClient from 'ddp-client';
let ddpClient = new DDPClient();
export default React.createClass({
/*
* Removed from snippet for brevity
*/
});
Next up let’s actually connect to the server.
app/index.js
/*
* Removed from snippet for brevity
*/
import DDPClient from 'ddp-client';
let ddpClient = new DDPClient();
export default React.createClass({
getInitialState() {
return {
connected: false,
posts: {},
};
},
componentDidMount() {
ddpClient.connect((err, wasReconnect) => {
let connected = true;
if (err) connected = false;
this.setState({ connected: connected });
});
},
/*
* Removed from snippet for brevity
*/
});
We should now be connected! You can do more with this but in the most basic sense, this is all you need to get started.
Making a Subscription from React Native
In app/index.js make your subscription. We’ll just update the state when the subscription has completed, for now.
app/index.js
/*
* Removed from snippet for brevity
*/
export default React.createClass({
getInitialState() {
return {
connected: false,
posts: {},
};
},
componentDidMount() {
ddpClient.connect((err, wasReconnect) => {
let connected = true;
if (err) connected = false;
this.setState({ connected: connected });
this.makeSubscription();
});
},
makeSubscription() {
ddpClient.subscribe('posts', [], () => {
this.setState({ posts: ddpClient.collections.posts });
});
},
/*
* Removed from snippet for brevity
*/
render() {
let count = Object.keys(this.state.posts).length;
return (
<View style={styles.container}>
<View style={styles.center}>
<Text>Posts: {count}</Text>
<Button text="Increment" onPress={this.handleIncrement} />
<Button text="Decrement" onPress={this.handleDecrement} />
</View>
</View>
);
},
});
That’s cool but what about the real time stuff? We’ll take care of that in the most basic way possible (this is just a starter guide after all).
app/index.js
/*
* Removed from snippet for brevity
*/
componentDidMount() {
ddpClient.connect((err, wasReconnect) => {
let connected = true;
if (err) connected = false;
this.setState({ connected: connected });
this.makeSubscription();
this.observePosts();
});
},
makeSubscription() {
ddpClient.subscribe("posts", [], () => {
this.setState({posts: ddpClient.collections.posts});
});
},
// This is just extremely simple. We're replacing the entire state whenever the collection changes
observePosts() {
let observer = ddpClient.observe("posts");
observer.added = (id) => {
this.setState({posts: ddpClient.collections.posts})
}
observer.changed = (id, oldFields, clearedFields, newFields) => {
this.setState({posts: ddpClient.collections.posts})
}
observer.removed = (id, oldValue) => {
this.setState({posts: ddpClient.collections.posts})
}
},
/*
* Removed from snippet for brevity
*/
Making a Method Call from React Native
What about adding and deleting posts from the RN app?
app/index.js
handleIncrement() {
ddpClient.call('addPost');
},
handleDecrement() {
ddpClient.call('deletePost');
},
You’ve now got a fully functioning, albeit very basic, React Native app backed by a Meteor app. I hope this gets you started down the pathway of developing Meteor and React Native apps together. You can (and should) take advantage of some of the frameworks out there to manage application state (Redux) and use React principles in developing you component hierarchy.
Challenge Question: How would you handle Optimistic UI?
One of the great things that comes out of the box with Meteor is the optimistic UI. At this time we don’t have that, out of the box, when using React Native. How would you go about implementing it in your application?
Checkout my followup post on how on how to authenticate a Meteor user from React Native: http://blog.differential.com/meteor-authentication-from-react-native/