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/

React Native School Logo

React Native School

Want to further level up as a React Native developer? Join React Native School! You'll get access to all of our courses and our private Slack community.

Learn More