Updated August 31, 2016

Setting up your Redux Store

Originally publish on medium.com.

If you read the articles I publish weekly you know that I’m a fan of React Native and Meteor. I use them everyday and I spend time teaching how to use them every night.

When you’re building a React or React Native app you’re likely going to end up using some flavor of Flux. I personally use Redux.

I’ll be doing a full in-depth overview of how to use Redux in a React Native app in my upcoming course (sign up for my email list for details!) but today I want to talk through setting up the store for your Redux implementation. It’s something that you’re rarely going to touch once it’s set up, can be confusing when you start a new project, and is pretty important.

So today I want to talk you through how I configure a store for Redux — purely opinion but it works for me :)

The Packages

I don’t want to overwhelm you but I do want to give you a sense of what we’re using and why we’re going to use it. There are definitely other options but this is what I’ve been using in my day job.

redux: You’re probably familiar with this one! We’re going to use createStore and applyMiddleware. createStore is pretty straightforward and applyMiddleware is there to tack on the middleware we’ll be using, covered below.

redux-immutable-state-invariant: Rather than using something like immutable.js to ensure immutability We’ll just be using this package to ensure we don’t accidentally mutate state. Immutable.js has a lot of benefits but it’s also got a learning curve — it’s up to you to make the decision.

redux-logger: Logs our previous state, the action that is dispatched, and the resulting state. It’s awesome to be able to track down what’s happening, when it’s happening, and the result of what happened.

redux-storage: Stores our state on the client so that some/all of it is available as soon as the client loads our app up the next time they visit us. This works for both React (via localStorage) and React Native (via AsyncStorage). We’re also going to touch on some of the extensions you can use.

Basic Setup

First thing we want to do is just get a basic store set up. We’re going to do a little extra here so we can integrate the other pieces easier. To the code!

store.js

import { createStore, applyMiddleware } from 'redux';
import reducers from '../reducers';

const middleWare = [];

const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
export function makeStore() {
  return createStoreWithMiddleware(reducers);
}

Adding Logging

Logging will be really easy to set up. Two things to note about this:

  1. The logger always has to be the last middleware added to the stack to function properly
  2. We’re passing the predicate in because we don’t want this middleware to run in the production environment. It’s purely a debugging tool.

Here’s the updated store file

store.js

import { createStore, applyMiddleware } from 'redux';
import reducers from '../reducers';
import createLogger from 'redux-logger';

// Setup
const middleWare = [];

// Logger Middleware. This always has to be last
const loggerMiddleware = createLogger({
  predicate: () => process.env.NODE_ENV === 'development',
});
middleWare.push(loggerMiddleware);

const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
export function makeStore() {
  return createStoreWithMiddleware(reducers);
}

0

Such pretty… much information

Adding Immutability Check

This is another really simple one to set up. We just need to require it and push it to the middleware stack. Again this is purely for debugging so we don’t want to include it in the production release.

store.js

const middleWare = [];

// Immutability Check
if (process.env.NODE_ENV === 'development') {
  middleWare.push(immutableCheckMiddleWare());
}

// Logger Middleware. This always has to be last
const loggerMiddleware = createLogger({
  predicate: () => process.env.NODE_ENV === 'development',
});
middleWare.push(loggerMiddleware);

const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
export function makeStore() {
  return createStoreWithMiddleware(reducers);
}

1

You know you goofed when you see this

Persist Redux Store

This will be our most complex middleware setup. First we’ll set it up in the most basic way — store after every action. I’m using the react native storage engine here because it means I can copy & past code from other projects. The same should apply for the web. I wrapped the new code in comment blocks so it’s easier to find.

store.js

import { createStore, applyMiddleware } from 'redux';
import reducers from '../reducers';
import createLogger from 'redux-logger';
import immutableCheckMiddleWare from 'redux-immutable-state-invariant';
/* ==== NEW CODE ==== */
import * as storage from 'redux-storage';
import createEngine from 'redux-storage-engine-reactnativeasyncstorage';
/* ==== END NEW CODE ==== */

// Setup
const middleWare = [];

// Immutability Check
if (process.env.NODE_ENV === 'development') {
  middleWare.push(immutableCheckMiddleWare());
}

// Redux Store
/* ==== NEW CODE ==== */
const wrappedReducer = storage.reducer(reducers);
let engine = createEngine('my-save-key');
// TODO: debounce
// TODO: filter
const reduxStorageMiddleware = storage.createMiddleware(engine);
middleWare.push(reduxStorageMiddleware);
const loadStore = storage.createLoader(engine);
/* ==== END NEW CODE ==== */

// Logger Middleware. This always has to be last
const loggerMiddleware = createLogger({
  predicate: () => process.env.NODE_ENV === 'development',
});
middleWare.push(loggerMiddleware);

const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
/* ==== NEW CODE ==== */
export function makeStore(callback) {
  const store = createStoreWithMiddleware(wrappedReducer);
  loadStore(store)
    .then((newState) => callback(null, newState))
    .catch(() => callback('Failed to load previous state', null));
  return store;
}
/* ==== END NEW CODE ==== */

Beyond the new pieces of code you can see that we modified the makeStore function to load the old store data. We also add a callback argument to the function so that you can do something once the old store has loaded. It follows the error, result pattern.

2

Improving Redux Storage — Adding Debounce and Filter

Right now the store that is persisted will be updated after every action is dispatched and the entire state tree will be copied. This can be inefficient if you’re dispatching a lot of actions in a small amount of time (like making multiple requests on app load). Fortunately we can extend redux-storage to account for that.

What we’re going to do is only persist the store once per second. This will help with the possibility of a lot of actions being dispatched in rapid succession.

Then we’re only going to store the auth key of store. You might want to use this if the data may be out of date quickly but you expect that a user session is going to succeed the next time they open the app.

Here’s the code

store.js

import { createStore, applyMiddleware } from 'redux';
import reducers from '../reducers';
import createLogger from 'redux-logger';
import immutableCheckMiddleWare from 'redux-immutable-state-invariant';
import * as storage from 'redux-storage';
import createEngine from 'redux-storage-engine-reactnativeasyncstorage';
/* ==== NEW CODE ==== */
import debounce from 'redux-storage-decorator-debounce';
import filter from 'redux-storage-decorator-filter';
/* ==== END NEW CODE ==== */

// Setup
const middleWare = [];

// Immutability Check
if (process.env.NODE_ENV === 'development') {
  middleWare.push(immutableCheckMiddleWare());
}

// Redux Store
const wrappedReducer = storage.reducer(reducers);
let engine = createEngine('my-save-key');
/* ==== NEW CODE ==== */
engine = debounce(engine, 1000);
engine = filter(engine, ['auth']);
/* ==== END NEW CODE ==== */
const reduxStorageMiddleware = storage.createMiddleware(engine);
middleWare.push(reduxStorageMiddleware);
const loadStore = storage.createLoader(engine);

// Logger Middleware. This always has to be last
const loggerMiddleware = createLogger({
  predicate: () => process.env.NODE_ENV === 'development',
});
middleWare.push(loggerMiddleware);

const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
export function makeStore(callback) {
  const store = createStoreWithMiddleware(wrappedReducer);
  loadStore(store)
    .then((newState) => callback(null, newState))
    .catch(() => callback('Failed to load previous state', null));
  return store;
}

That’s a pretty easy to set up store that will carry you a long way. There’s more you can do if you want but I haven’t ventured beyond that.

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