Redux vs. MobX by example — Part I: Intro, and Exploring Redux

Redux vs. MobX by example — Part I: Intro, and Exploring Redux

Redux and MobX are popular libraries, for managing the state in JavaScript apps. Even though they are typically used alongside React; they are framework agnostic, and can be used with any JS setup.

While Redux’s tagline is “Predictable state container for JavaScript apps” … MobX rolls with “Simple, scalable state management”. We will come back to these descriptions later.

Over the length of two stories, we will be creating a Redux implementation, and then a MobX implementation, of the ES6 + React Todo app, that we built in our last story … we will also explore the concepts behind these state management libraries while we do so.

After going through both the Redux and MobX implementations, we will conclude in the second story with an abstract level discussion of the differences in solutions that these state management libraries have to offer, based on what we learn about each of these.

Here's an overview of what we will cover in this story

  • Short Recap
  • Redux — The predictable state container
    • The Three Principles of Redux
    • Redux Store — Single Source of Truth
    • Actions in Redux — State is read only
    • Reducers in Redux — Changes are made with pure functions
    • Wiring the Redux Store with the components

Short Recap

If you already have a working app from the past story, then you can continue from there, or you could optionally clone this repository for a starting point. Don’t forget to npm install if you clone the repo.

$ git clone https://github.com/fatman-/minimal-todo-react.git
$ cd minimal-todo-react
$ npm install

Start the app, to see everything is working, before moving ahead

$ npm start

Here is the directory structure we have, without having made any changes so far:

 ├── minimal_todo_react
  ├── dist
  │   ├── index.html
  │   └── style.css
  ├── node_modules
  ├── src
  │   ├── components
  │       ├── SingleTodo.js
  │       ├── TodoApp.js
  │       └── VisibleTodoList.js
  │   ├── lib
  │       ├── Todo.js
  │       └── TodoDataInterface.js
  │   └── index.js
  ├── .gitignore
  ├── package.json
  └── webpack.config.js

To give you a short recap, we’re using Babel and Webpack to transform, and bundle our ES6 code. We have a Todo class definition in Todo.js for instantiating new Todo objects; and a TodoDataInterface class definition in TodoDataInterface.js for instantiating a TodoDataInterface object; for storing, and manipulating an array of Todo objects.

If you are unsure about any part of the code, you can revisit this story for all the implementation details of the app.

Let’s go ahead and get started with implementing Redux in our app.


Redux — The predictable state container

Let’s make a new branch named redux-implemenation so as to keep our “Redux” changes decoupled from our ES6 + “Only React” implementation; and install redux and react-redux.

$ git checkout -b redux-implementation
$ npm install --save redux react-redux

NOTE: If you have cloned the repo from here; be advised not to use the names “redux” or “mobx”, for your branches, as the cloned repo already contains branches with these names.

The whole gist of Redux can be summarised using the following three principles. Read them out loud, before moving ahead … where we will see how these principles transform to Redux code.

The Three Principles of Redux

  • Single Source of Truth — The whole state of your app is stored in an object tree inside a single store.
  • State is read only — The only way to change the state tree is to emit an action, an object describing what happened.
  • Changes are made with pure functions — To specify how the actions transform the state tree, you write pure reducers.

Redux Store — Single Source of Truth

Since all the data is going to be stored in an immutable store, and changes to it can only be done by emitting actions, we don’t need TodoDataInterface.js anymore.

From the project’s ./src/lib directory

$ rm TodoDataInterface.js

Let’s reflect the above change in our main index.js file.

./src/index.js before

import React from 'react';
import ReactDOM from 'react-dom';

import TodoDataInterface from './lib/TodoDataInterface';
import TodoApp from './components/TodoApp';

const todoDataInterface = new TodoDataInterface();
ReactDOM.render(
    <TodoApp dataInterface={todoDataInterface}/>,
    document.getElementById('app')
);

./src/index.js after

import React from 'react';
import ReactDOM from 'react-dom';

import initializeStore from './store/initializeStore';
import TodoApp from './components/TodoApp/';

const store = initializeStore({todos: [], visibilityFilter: "ALL_TODOS"});
ReactDOM.render(<TodoApp store={store}/>, document.getElementById('app'));

Instead of a TodoDataInterface we’re initialising a store, using an imported initializeStore() function to manage our state. Additionally, we’re passing the initialState of the app to the store via an argument to the initialStore() function.

Let’s create initializeStore()

From the project’s ./src directory

$ mkdir store
$ cd store
$ touch intializeStore.js

./src/store/initializeStore.js

import { createStore } from 'redux';
import rootReducer from '../reducers/index';

export default function initializeStore(initialState) {
    return createStore(rootReducer, initialState);
}

Redux provides us with a createStore function which takes in a reducer , and an initialState, as arguments; and returns a “container” object with methods to aid in accessing and manipulating the data present in this container/store object.

As an instance, when we do this:

const store = initializeStore({todos: [], visibilityFilter: "ALL_TODOS"});

…we have access to the state tree, using the getState method; like so: store.getState()

We will discuss on what a reducer is, in detail … but to give you a gist, it is a pure function describing all the changes that have to be made in the store; for a given action. Don’t worry if that doesn’t make sense, it soon will.

To understand reducers, we need to understand actions. Based on what we have read in the principles of Redux, the only way to manipulate the store is to emit an action, an object with information on what happened.

Actions in Redux — State is read only

Actions in Redux are just plain JavaScript objects, that contain the information needed to update the store. We can structure an action object however we see fit.

As a de-facto Redux convention, you would see the action objects — in a typical Redux app, i.e., — with at the least a “type” key, used to describe the type of the action; and more often than not, corresponding data associated with the action.

So an action object describing an addition of a Todo object to our store would look something like this:

{
  type: "ADD_TODO",
  todo: MyTodoObject
}

Actions are typically structured as action creators, which are just functions that return action objects.

function addTodo(todo) {
    return {
        type: actionTypes.ADD_TODO
        todo
    }
}

So when I invoke addTodo(myTodoObject) the same action object as the one above the addTodo() action creator definition, is created.

You might have also noticed a change in the value of the ‘type’ key in the addTodo() action creator. Since all action types are anything but constants, it is a good practice to store them along-side other constants used in our app.

Let’s create a dedicated folder for all the constants in our app. The visibility filters that we have seen earlier, can also live inside this folder.

From the project’s ./src folder:

$ mkdir constants
$ touch actionTypes.js
$ touch visibilityFilters.js

./src/constants/actionTypes.js

export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
export const COMPLETE_TODO = 'COMPLETE_TODO';
export const CHANGE_VISIBILITY_FILTER = 'CHANGE_VISIBILITY_FILTER';

./src/constants/visibilityFilter.js

export default ["ALL_TODOS", "ACTIVE_TODOS", "COMPLETED_TODOS"];

Having done that let’s define all of the possible action creators, for our app.

From the project’s ./src folder:

$ mkdir actions
$ touch index.js

./src/actions/index.js

import * as actionTypes from '../constants/actionTypes'

export function addTodo(todo) {
    return {
        type: actionTypes.ADD_TODO,
        todo
    }
}

export function removeTodo(todoId) {
    return {
        type: actionTypes.REMOVE_TODO,
        todoId
    }
}

export function completeTodo(todoId) {
    return {
        type: actionTypes.COMPLETE_TODO,
        todoId
    }
}

export function changeVisibilityFilter(visibilityFilter) {
    return {
        type: actionTypes.CHANGE_VISIBILITY_FILTER,
        visibilityFilter
    }
}

Along with the getState method, we have seen earlier, Redux stores provide a few other methods; one among them is the dispatch method.

When we do store.dispacth(addTodo(MyTodoObject) the corresponding action object is dispatched to the Redux store.

Next in line would be a reducer, a pure function specifying how this action transforms the state tree.

Reducers in Redux — Changes are made with pure functions

Reducers are pure functions that return the next state, given the current state and an action object.

function visibilityFilter(state = "ALL_TODOS", action) {
    switch (action.type) {
        case actionTypes.CHANGE_VISIBILITY_FILTER:
            return action.visibilityFilter;
        default:
            return state;
    }
}

This is as simple as a reducer function definition could get.

Dispatching following action object: {type: "CHANGE_VISIBILITY_FILTER", visibilityFilter: "COMPLETED_TODOS"} …would invoke the visibilityFilter(), returning "COMPLETED_TODOS" and state.visibilityFilter would be set with this return value.

But how does this visibilityFilter reducer know that it has to change visibilityFilter part of the state?

One of the key aspects of Redux is reducer composition, what this means is that we can write different reducer functions for all the different parts of our state.

Redux provides a handy combineReducers function which takes an object mapping the parts of the state to the corresponding reducer functions.

So you could do this:

combineReducers({
    todos: todosReducer,
    visibilityFilter: visibilityFilterReducer
});

Since all our reducer functions are named same as the parts of the state they are handling, we can change the above to the following, thanks to ES6’s object literal property value shorthand:

combineReducers({
    todos,
    visibilityFilter
});

combineReducers returns a single function that invokes all the reducers passed to it whenever an action is dispatched. You can read this answer for a little more context on how combineReducers work.

From the project’s ./src folder

$ mkdir reducers
$ cd reducers
$ touch index.js
$ touch todos.js
$ touch visibilityFilter.js

Combine reducers in the ./src/reducers/index.js file

import { combineReducers } from 'redux';
import todos from './todos';
import visibilityFilter from './visibilityFilter';

export default combineReducers({
    todos,
    visibilityFilter
});

Let’s define the required, individual reducer functions.

./src/reducers/todos.js

import * as actionTypes from '../constants/actionTypes';
import Todo from '../lib/Todo'

const initialState = [];

export default function todos(state = initialState, action) {
    switch (action.type) {
        case actionTypes.ADD_TODO:
            return [...state, action.todo];
        case actionTypes.REMOVE_TODO:
            return state.filter(todo => todo.id !== action.todoId);
        case actionTypes.COMPLETE_TODO:
            return state.map(todo => {
                if (todo.id === action.todoId) {
                    return Object.assign({}, todo, {isDone: !todo.isDone})
                }
                return todo
            });
        default:
            return state;
    }
}

./src/reducers/visibilityFilter.js

import * as actionTypes from '../constants/actionTypes';

const initialState = "ALL_TODOS";

export default function visibilityFilter(state = initialState, action) {
    switch (action.type) {
        case actionTypes.CHANGE_VISIBILITY_FILTER:
            return action.visibilityFilter;
        default:
            return state;
    }
}

Wiring the Redux Store with the components

No changes whatsoever are needed in the SingleTodo.js and VisibleTodoList.js files. Proper componentizing FTW!

The TodoApp component earlier was managing its own state; and with every manipulation it was reflecting the changes through the todoDataInterface prop; before updating its state with the updated changes.

We need to change the TodoApp component to connect with the Redux store, and update it so as the manipulations are reflected in the Redux store.

To do this, we will split TodoApp into two component patterns; one of them will be a “presenter” stateless component, and the other, a “container” component.

The presenter will only be responsible for describing the structure of the component based on the props passed to it.

The container component will be responsible for connecting to the Redux store and passing the required parts of the state, and corresponding dispatch actions down to the presenter component, as props.

Let’s delete the old component and make the required new components.

From the project's ./src/components folder

$ rm TodoApp.js
$ mkdir TodoApp
$ cd TodoApp
$ touch presenter.js
$ touch index.js

./src/components/TodoApp/index.js

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import TodoApp from './presenter';

function mapStateToProps(state) {
    const { todos, visibilityFilter } = state;
    return {
        todos,
        visibilityFilter
    }
}

function mapDispatchToProps(dispatch) {
    return {
        addTodo: bindActionCreators(actions.addTodo, dispatch),
        removeTodo: bindActionCreators(actions.removeTodo, dispatch),
        completeTodo: bindActionCreators(actions.completeTodo, dispatch),
        changeVisibilityFilter: bindActionCreators(actions.changeVisibilityFilter, dispatch)
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);

The connect function provided by react-redux returns a higher order component; wrapped over the TodoApp presenter component; and it makes the required parts of the state, and action creators (bound with the dispatch method) available to this presenter component as props.

So calling this.props.addTodo will also invoke the dispatch method of the store on the corresponding returned action object.

./src/components/TodoApp/presenter.js

import React from 'react';
import VisibleTodoList from '../VisibleTodoList';
import visibilityFilters from '../../constants/visibilityFilters'

import Todo from '../../lib/Todo';

function visibleTodos(todos, visibilityFilter) {
    switch (visibilityFilter) {
        case "ALL_TODOS":
            return todos;
        case "ACTIVE_TODOS":
            return todos.filter(todo => todo.isDone === false);
        case "COMPLETED_TODOS":
            return todos.filter(todo => todo.isDone === true);
        default:
            return todos;
    }
}

export default class TodoApp extends React.Component {
    render() {

        const {
            todos,
            visibilityFilter,
            addTodo,
            removeTodo,
            completeTodo,
            changeVisibilityFilter
        } = this.props;

        let visibleTodosArray = visibleTodos(todos, visibilityFilter);

        return (
            <div>
                <h2> Minimal TodoApp built with React and Redux </h2>
                <input
                    type="text"
                    placeholder="What do you want todo?"
                    ref={(c => this._todoInputField = c)}
                />
                <button
                    onClick={() => addTodo(new Todo(this._todoInputField.value))}>
                        Add Todo
                </button>
                <VisibleTodoList
                    visibleTodos={visibleTodosArray}
                    visibilityFilter = {visibilityFilter}
                    completeTodo={completeTodo}
                    removeTodo={removeTodo}
                />
                <div>
                    SHOW:
                    {
                        visibilityFilters.map(
                            visibilityFilter =>
                                <button
                                    key={visibilityFilter}
                                    onClick={() => changeVisibilityFilter(visibilityFilter)}>
                                        {visibilityFilter.replace("_", " ")}
                                </button>
                        )
                    }
                </div>

            </div>
        );
    }
}

That’s about it, and we have a working Redux app. 👏🏼

The second part of the story with a MobX implementation of the same app, and a conclusion with the Redux vs. MobX differences ... is now out.