Updated June 1, 2016

How I use React Native, Redux, and Meteor

Originally publish on medium.com.

Note: What I’ll be talking about here only covers using Meteor and Redux in the context of a React Native client.

I originally set out to build a package on top of my React Native Meteor boilerplate that would make using React Native and Redux, backed by Meteor, trivial to implement.

As I started building I realized that what I was building wasn’t what I actually used. I don’t like to build solutions that don’t align with what I actually use, so I scrapped that.

Rather than trying to build out a package that does a lot of magic I want to walk you through how I actually use Redux in combination with Meteor and React Native. It’s not perfect but it’s what I’m actually using to build real apps, everyday.

I’ll cover optimistic updates, storing Meteor state in the Redux store, how I access data, and why I actually go through the trouble.

If you’re not familiar with Redux a lot of this may not make sense to you. I suggest reading through the docs before going on.

Optimistic Updates through Action

If you’ve looked at my boilerplate you’ve likely seen this

SignIn.js

handleSignIn() {
  if (this.validInput(true)) {
    const { email, password } = this.state;
    Meteor.loginWithPassword(email, password, (err) => {
      if (err) {
        this.setState({ error: err.reason });
      }
    });
  }
}

That’s a snippet from here. Now this can work perfectly well but your components may start to get littered with logic… especially when an optimistic update is involved.

So rather than putting a lot of code into my container components I use them to simply dispatch a variety of actions — actions that fetch data and actions that will cause some modification of data.

To do this you’ll also need to use the redux-thunk library because each action dispatched from a component will itself dispatch two actions. Let’s see an example…

So in my container component I’ll have a function that, upon press, will increment the count of a number.

IncrementContainer.js

handleIncrement() {
  this.props.dispatch(incrementCount());
}

Now what will the incrementCount action actually look like?

actions.js

const incrementCountOptimistic = (count) => {
  return {
    type: 'INCREMENT_COUNT_OPTMISTIC',
    count,
  };
};

const incrementCountConfirm = (count) => {
  return {
    type: 'INCREMENT_COUNT_CONFIRM',
    count,
  };
};

export const incrementCount = () => {
  return (dispatch, getState) => {
    const count = getState().count;
    dispatch(incrementCountOptimistic(count));

    Meteor.call('Count.increment', (err, res) => {
      if (res) {
        dispatch(incrementCountConfirm(res.count));
      }
    });
  };
};

You can see that the action creator does 3 things. First it dispatches another action that will handle our optimistic update, then it makes the actual meteor method call, finally it will update the store with the correct result.

Now this is useless without the actual reducer so let’s take a look at that

reducer.js

const initialState = {
  count: 0,
};

const countReducer = (state = initialState, action) => {
  switch (action.type === '') {
    case 'INCREMENT_COUNT_OPTMISTIC':
      return Object.assign({}, state, { count: action.count + 1 });
    case 'INCREMENT_COUNT_CONFIRM':
      return Object.assign({}, state, { count: action.count });
    default:
      return state;
  }
};

Obviously this is an extremely simple example but you can start to get the idea. This can get tedious but you’re in total control of the process AND you only have to implement it as you see fit — maybe you don’t need an optimistic update now… just don’t build it. But when you do you only have to create a new action and then modify your reducer to do something with it.

I love that I can improve functionality of my app without having to mix the view logic and my business logic. Redux brings that nice separation of concerns.

Copying Data from Meteor to Redux

This is one area that I’m sure can be improved and I encourage people to give me ideas on how to do so. Sometimes you want to keep some of the Meteor data in your redux store. The reason I want to copy it over is so that every component can look the same and draws it’s data from Redux, rather than having some the use Redux, some that use the meteor createContainer, and some that use both.

These pieces of data are Meteor.user(), Meteor.status(), and Meteor.loggingIn(). I want my redux store to always have the latest result of that.

First we need to connect a component to be knowledgable of Meteor. We’re only going to do this in our root component. Whenever the props change we’ll update our Redux store.

MeteorConnect.js

import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { createContainer } from 'react-native-meteor';
import { connect } from 'react-redux';
import { updateMeteorData } from '../actions';

class MyApp extends Component {
  updateReduxStore(props) {
    this.props.dispatch(
      updateMeteorData(props.user, props.status, props.loggingIn)
    );
  }

  render() {
    this.updateReduxStore(this.props);

    return (
      <View>
        <Text>
          This is my app. There are many like it but this one is mine.
        </Text>
      </View>
    );
  }
}

const MyConnectedApp = createContainer(() => {
  return {
    users: Meteor.user(),
    status: Meteor.status(),
    loggingIn: Meteor.loggingIn(),
  };
}, MyApp);

export default connect()(MyConnectedApp);

Beyond that we need to actually write the action creator, which is pretty simple so I’ll leave it out, and our reducer. You can obviously name things however you want.

meteorReducer.js

const initialState = {
  user: null,
  loggingIn: false,
  status: {},
};

const countReducer = (state = initialState, action) => {
  switch (action.type === '') {
    case 'UPDATE_METEOR_DATA':
      return Object.assign({}, state, {
        user: action.user,
        loggingIn: action.loggingIn,
        status: action.status,
      });
    default:
      return state;
  }
};

All of your Meteor data is now in your Redux store and should be up to date at all times.

If you have a better way of doing this please let me know. There is room for improvement here.

Accessing the Data

Now you may notice that by doing this you no longer have access to Minimongo. To me, and for my purposes, that’s okay. By doing things like this we’re able to access and store data in the same way throughout our entire app.

I use lodash to query my data. That’s a tradeoff you’ll have to decide for yourself.

Why the Hassle?

Clearly there is extra work here and it’s definitely not the right fit for everyone. The reason I’m doing it in this way is to keep consistency between my entire application and the greater React ecosystem. I’m building the React Native app as a React app first and a Meteor app second. As you start to build an app in this way you realize that the only places your app actually has any knowledge of Meteor is in your actions and wherever you copy data from Meteor to Redux. This makes it easy to modify code and know where to look when a bug arises.

Build as a React Native app first and a Meteor app second.

It also makes it easier to make upgrades. Say you put together a dedicate search service for your app and it has a REST API. You’ve only got to modify your action to hit that new URL rather than dive into your view logic, risking issues.

Now you may be saying there is a lot more boilerplate code using this method and I won’t disagree with you. But I’m okay writing 10 more lines of code if it means I can find and fix bugs 10x faster. When you’re working on an app for months or years at a time you’ll spend more time refactoring and modifying existing code than writing new code so make it easy to do that.

With that being said this isn’t a perfect process and there is a lot of room for improvement. I would love to hear your thoughts on how to make this more straightforward.

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