Background Cover
November 6, 2016
5 min read

Redux, a predictable state container for JS apps

jsjavascript data structures redux

Redux

Redux is a predictable state container for JavaScript apps.

To understand Redux we need to dive into a few concepts:

  • Actions
  • Action Creators
  • Action Types
  • Reducers
  • Stores

The three principles

These are the three principles to build applications with Redux. The following are specified in the Redux Documentation

Single source of truth

The state of your whole application is stored in an object tree within a single store. We are going to represent the whole state of our application in a single Javascript Object.

State is read-only

The only way to change the state is to emit an action, an object describing what happened.

Changes are made with pure functions

To specify how the state tree is transformed by actions, you write pure reducers.

Actions

Actions describe that something happened in our application. They are payloads of information that send data to your store. They are the only source of information for the store.

Here is an example:

const ADD_COMMENT = 'ADD_COMMENT';

{
  type: ADD_COMMENT,
  comment: 'This is my comment.'
}

Actions are JavaScript objects. Every action must have a type property that indicates the type of action being performed. Types should be defined as constants.

Once an app becomes big enough, you may want to move them into a separate module. We store them in a contants.js file. auth.js Constants

import { ADD_COMMENT, REMOVE_COMMENT } from "./constants";

We can dispatch an action by using dispatch().

Our actions live within the coral-framework/actions folder. talk/client/coral-framework/actions

More about Actions: Actions · Redux

Async Actions

For our async operations we dispatch three actions.

  • <ACTION_TYPE>_REQUEST

  • <ACTION_TYPE>_SUCCESS

  • <ACTION_TYPE>_FAILURE

Request

We use the postfix _REQUEST to know that the resource is being requested.

Success

We use the postfix _SUCCESS to know that the resource response came back successfully.

Failure

We use the postfix _FAILURE to know that the resource request failed.

Action Creators

Action Creators are functions that return actions. This makes it easier to use, portable and testable.

function addComment(comment) {
  return {
    type: ADD_COMMENT,
    comment,
  };
}

So we can later trigger those actions by using dispatch()

dispatch(addComment(comment));
dispatch(removeComment(comment.id));

Dispatch Function

The dispatch() function can be accessed directly from the store as store.dispatch(), but more likely you'll access it using a helper like react-redux'sconnect().

We use connect()in our containers. More about this in Architecture.

Reducers

With Actions we describe that something happened in our application. But we don’t specify how our state will be modified with this change.

In a Reducer we will specify how the state of our application change when an action has been dispatched.

Here we also will want to specify the initialState

Before building reducers it’s important to that you: - Don’t mutate the state - Return the previous state in the default case.

Here is an example of an auth reducer:

const initialState = {
  isLoading: false,
  loggedIn: false,
  user: null,
  error: "",
};

function auth(state = initialState, action) {
  switch (action.type) {
    case actions.CHECK_LOGIN_REQUEST:
      return Object.assign({}, state, {
        isLoading: true,
      });
    case actions.CHECK_LOGIN_SUCCESS:
      return Object.assign({}, state, {
        isLoading: false,
        loggedIn: true,
        user: action.user,
        error: "",
      });
    case actions.CHECK_LOGIN_FAILURE:
      return Object.assign({}, state, {
        isLoading: false,
        error: action.error,
        loggedIn: false,
        user: null,
      });
    default:
      return state;
  }
}

Notice that a reducer takes the state as first argument and when it’s not defined it returns the initialState. As a second argument it takes the action. We have our state and we have the action. This is the time to specify how we modify the state.

Reducers using ImmutableJS

We are using ImmutableJS to maintain our app state. Here is a guide on how to use ImmutableJS.

This is how a simplified version of our auth reducer looks like:

const initialState = Map({
  isLoading: false,
  loggedIn: false,
	user: null,
	error: ‘’
});

function auth (state = initialState, action) {
  switch (action.type) {
    case CHECK_LOGIN_REQUEST:
    	return state
      	.set('isLoading', true);
    case CHECK_LOGIN_SUCCESS:
    	return state
      	.set('isLoading', false)
      	.set('loggedIn', true)
      	.set('user', action.user)
      	.set('error', '');
      });
    case CHECK_LOGIN_FAILURE:
    	return state
      	.set('isLoading', false)
			.set('error', action.error)
			.set('loggedIn', false)
			.set('user', null)
      });
    default:
      return state
  }
}

Looks cleaner, right?

It’s pretty easy to follow. Here it says if a CHECK_LOGIN_REQUEST action has been dispatched set the isLoading from our state to true. And we can show a tiny loader to let the user now we are requesting something to the server.

Our actions live within the coral-framework/reducers folder. talk/client/coral-framework/reducers

More about Reducers: Reducers · Redux

And the last thing we need to see is the Store

Store

The Store is what holds the application state. Here we can access and update the state.

It’s important to note that we will only have a single store in our application called rootReducer and we will use reducer composition instead of many stores.

Here is an example of how create a store with createStore() using a reducer:

import { createStore } from "redux";
import authReducer from "./auth";
let store = createStore(authReducer);

We do have a lot of stores so we will need to combine all our reducers with combineReducers() within a single store

import {combineReducers} from 'redux';

import authReducer from './auth'
import configReducer from './config'
import userReducer from './user'

const rootReducer = combineReducers({
  authReducer,
  configReducer,
  userReducer
	...
});

More about Stores: Store · Redux

Useful Resources

Redux Documentation · Redux Getting Started with Redux Usage with React · Redux