Marcell Huszti
11th December 2017
6 min. read

Managing state in an application is critical, and is often done haphazardly. In the following article Marci would like to give you a short intro about Redux, that provides a state container for JS apps that will help them behave consistently.

What is it?

Redux is a predictable state container for JavaScript apps. In other words, it is a handy place to store and manipulate your data, follow its state at any given moment.

Why use it?

As I wrote, you can follow the state of your data step-by-step, you can edit it on the go, the changes are predictable, so the steps are easily reproducible, and most importantly it is fairly easy to use.

Redux fits in nicely with React (and therefor React Native).

How to use it?

In itself, Redux is simple. Your app’s state can be imagined as a simple plain object, for example, a product browsing app, using filters:

{
  products: [
    {
      id: 1,
      name: "Something",
      category: "fishing",
      size: "large"
    },
    {
      id: 2,
      name: "Something else",
      category: "fishing",
      size: "medium"
    },
    {
      id: 3,
      name: "A third thingy",
      category: "hunting",
      size: "large"
    }
  ],
  filters: [
    {
      categories: ["fishing"]
    },
    {
      sizes: ["small"]
    }
  ]
}

This object is like a “model” for your app, except there are no setters. This restricts most parts of your app to make changes to it, narrowing the possibilties of “hard-to-reproduce” bugs.

To change the state, you must dispatch an action. An action is also a plain Javascript object, describing what needs to be changed.

{ type: "REMOVE_FILTER", filter: "sizes" }
{ type: "ADD_FILTER", filter: "categories", values: ["hunting"] }
{ type: "ADD_PRODUCT", products: [...]}

Notice that these objects have one thing in common: the type property.

To channel the actions into the state, we have to have to write a function, let’s call it a reducer. This function gets 2 arguments:
the current state object, and the action, and it has to return the new state object.
Writing this function for big apps, would become very hard to manage very fast, so we break it up into smaller, more manageable parts:

function filters(state = {}, action) {
  switch (action.type) {
    case "REMOVE_FILTER": {
      delete state.filters[action.filter];
      return {
        ...state
      }
    }
    case "ADD_FILTER": {
      return {
        ...state,
        filters: [...state.filters, {[action.filter]: action.values}]
      }
    }
    default:
      return state;
  }
}

function products(state = {}, action) {
  if (action.type === "ADD_PRODUCT") {
    return {
      ...state,
      products: [...state.products, ...action.products]
    }
  } else {
    return state;
  }
}

Then we can combine these substates in another reducer, with corresponding keys:

function reducer(state = {}, action) {
  return {
    products: products(state.products, action),
    filters: filters(state.filters, action),
  }
}

And basicly that’s it.

Note: you can’t return void or undefined from a reducer, it always has to be a defined object.

This is fine, but how do I use it in my app?
You can use Redux with React Angular, Ember, jQuery or vanilla JS. Since I mostly work with React Native, I’m going to show it through React, and for that, we are going to need the react-redux package.

For us to be able to read the state tree, we need to implement a container component. A container component is a component that has access to the state of the app.
In short, you could just create your own container component using store.subscribe(), but there is a better way.
In the package there is a handy function, called connect(). This function provides many useful optimizations to prevent unnecessary re-renders. (One result is that you don’t have to implement shouldComponentUpdate()).
This function takes 2 functions as arguments:
mapStateToProps() and mapDispatchToProps().

class SomeComponent {

}

const mapStateToProps = state => {
  return {
    products: state.products,
    filters: state.filters
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    addFilter: (filter, values) => {
      dispatch({type: "ADD_FILTER", filter, values})
    }
  }
}

export const SomeConnectedComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(SomeComponent)

The mapStateToProps() function will get the app state as a parameter, and returns an object with connected substates, which will be present on the props property of the component.
The mapDispatchTpProps() function will receive the dispatch() function as a parameter and returns an object with set actions, which will be present on the props property of the component.

And that’s it. Now set parts of the state of the app and connected actions will be present in the component. The component will be smartly updated whenever a change in the state will require it, you don’t have to do anything else with it.

Things to be aware of

The state of your application is an immutable object.

The only way to change the state is dispatching an action. Every action will run through all of the reducers. So how will we know what to change where? Remember the type property?
Every action has to have it. It is this property upon which it will be decided what part of the state will change.
From these, it follows that the type has to be unique for every action you’re going to take in the app.
This allows Redux to detect changes in the state, and only update specific views. Because of this, it is important that you return a new object from your reducers every time you change something in them and return an unchanged object if that reducer did nothing.

Performance

In the past, we ran into a problem, where too many components were connected to the state, and the app became sluggish. We solved that by only connecting the state into views (top-level components), and everything from got the data they needed from the views.
Connecting dispatches into components didn’t hinder performance

Get our free newsletter