JavaScript
Article

How to Build a Todo App Using React, Redux, and Immutable.js

By Dan Prince

This article was rewritten on 3rd May, 2016. Comments relating to the original article have been removed.

The way React uses components and a one-way data flow makes it ideal for describing the structure of user interfaces, however its tools for working with state are kept deliberately simple; to help remind us that React is just the View in the traditional Model-View-Controller architecture.

There’s nothing to stop us from building large applications with just React, but we would quickly discover that to keep our code simple, we’d need to manage our state elsewhere.

Whilst there’s no official solution for dealing with application state, there are some libraries that align particularly well with React’s paradigm. Today we’ll pair React with two such libraries and use them to build a simple application.

Redux

Redux is a tiny library that acts as a container for our application state, by combining ideas from Flux and Elm. We can use Redux to manage any kind of application state, providing we stick to the following guidelines:

  1. Our state is kept in a single store
  2. Changes come from actions not mutations

At the core of a Redux store is a function that takes the current application state and an action and combines them to create a new application state. We call this function a reducer.

Our React components will be responsible for sending actions to our store and in turn our store will tell the components when they need to re-render.

ImmutableJS

Because Redux doesn’t allow us to mutate the application state, it can be helpful to enforce this by modeling application state with immutable data structures.

ImmutableJS offers us a number of immutable data structures with mutative interfaces and they’re implemented in an efficient way, inspired by the implementations in Clojure and Scala.

Demo

We’re going to use React with Redux and ImmutableJS to build a simple todo list that allows us to add todos and toggle them between complete and incomplete.

See the Pen React, Redux & Immutable Todo by SitePoint (@SitePoint) on CodePen.

The code is also available on GitHub.

Setup

We’ll get started by creating a project folder and initializing a package.json file with npm init. Then we’ll install that dependencies that we’re going to need.

npm install --save react react-dom redux react-redux immutable
npm install --save-dev webpack babel-loader babel-preset-es2015 babel-preset-react

We’ll be using JSX and ES2015, so we’ll compile our code with Babel and we’re going to do this as part of the module bundling process with Webpack.

First we’ll create our Webpack configuration in webpack.config.js.

module.exports = {
  entry: './src/app.js',
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: { presets: [ 'es2015', 'react' ] }
      }
    ]
  }
};

And finally we’ll extend our package.json by adding an npm script to compile our code with source maps.

"scripts": {
  "build": "webpack --debug"
}

We’ll need to run npm run build each time we want to compile our code.

React & Components

Before we implement any components, it can be helpful to create some dummy data. This helps us get a feel for what we’re going to need our components to render.

const dummyTodos = [
  { id: 0, isDone: true,  text: 'make components' },
  { id: 1, isDone: false, text: 'design actions' },
  { id: 2, isDone: false, text: 'implement reducer' },
  { id: 3, isDone: false, text: 'connect components' }
];

For this application, we’re only going to need two React components, <Todo /> and <TodoList />.

// src/components.js

import React from 'react';

export function Todo(props) {
  const { todo } = props;
  if(todo.isDone) {
    return <strike>{todo.text}</strike>;
  } else {
    return <span>{todo.text}</span>;
  }
}

export function TodoList(props) {
  const { todos } = props;
  return (
    <div className='todo'>
      <input type='text' placeholder='Add todo' />
      <ul className='todo__list'>
        {todos.map(t => (
          <li key={t.id} className='todo__item'>
            <Todo todo={t} />
          </li>
        ))}
      </ul>
    </div>
  );
}

At this point, we can test these components by creating an index.html file in the project folder and populating it with the following markup. (You can find a simple stylesheet here on GitHub).

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css">
    <title>Immutable Todo</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="bundle.js"></script>
  </body>
</html>

We’ll also need an application entry point at src/app.js.

// src/app.js

import React from 'react';
import { render } from 'react-dom';
import { TodoList } from './components';

const dummyTodos = [
  { id: 0, isDone: true,  text: 'make components' },
  { id: 1, isDone: false, text: 'design actions' },
  { id: 2, isDone: false, text: 'implement reducer' },
  { id: 3, isDone: false, text: 'connect components' }
];

render(
  <TodoList todos={dummyTodos} />,
  document.getElementById('app')
);

Compile the code with npm run build, then navigate your browser to the index.html file and make sure that it’s working.

Redux & ImmutableJS

Now that we’re happy with the user interface, we can start to think about the state behind it. Our dummy data is a great place to start from and we can easily translate it into ImmutableJS collections.

import { List, Map } from 'immutable';

const dummyTodos = List([
  Map({ id: 0, isDone: true,  text: 'make components' }),
  Map({ id: 1, isDone: false, text: 'design actions' }),
  Map({ id: 2, isDone: false, text: 'implement reducer' }),
  Map({ id: 3, isDone: false, text: 'connect components' })
]);

ImmutableJS maps don’t work in the same way as JavaScript’s objects, so we’ll need to make some slight tweaks to our components. Anywhere there was a property access before (e.g. todo.id) needs to become a method call instead (todo.get('id')).

Designing Actions

Now that we’ve got the shape and structure figured out, we can start thinking about the actions that will update it. In this case, we’ll only need two actions, one to add a new todo and the other to toggle an existing one.

Let’s define some functions to create these actions.

// src/actions.js

// succinct hack for generating passable unique ids
const uid = () => Math.random().toString(34).slice(2);

export function addTodo(text) {
  return {
    type: 'ADD_TODO',
    payload: {
      id: uid(),
      isDone: false,
      text: text
    }
  };
}

export function toggleTodo(id) {
  return {
    type: 'TOGGLE_TODO',
    payload: id
  }
}

Each action is just a JavaScript object with a type and payload properties. The type property helps us decide what to do with the payload when we process the action later.

Designing a Reducer

Now that we know the shape of our state and the actions that update it, we can build our reducer. Just as a reminder, the reducer is a function which takes a state and an action, then uses them to compute a new state.

Here’s the initial structure for our reducer.

// src/reducer.js

import { List, Map } from 'immutable';

const init = List([]);

export default function(todos=init, action) {
  switch(action.type) {
    case 'ADD_TODO':
      // ...
    case 'TOGGLE_TODO':
      // ...
    default:
      return todos;
  }
}

Handling the ADD_TODO action is quite simple, as we can use the .push() method, which will return a new list with the todo appended at the end.

case 'ADD_TODO':
  return todos.push(Map(action.payload));

Notice that we’re also converting the todo object into an immutable map before it’s pushed onto the list.

The more complex action we need to handle is TOGGLE_TODO.

case 'TOGGLE_TODO':
  return todos.map(t => {
    if(t.get('id') === action.payload) {
      return t.update('isDone', isDone => !isDone);
    } else {
      return t;
    }
  });

We’re using .map() to iterate over the list and find the todo whose id matches the action. Then we call .update() method, which takes a key and a function, then it returns a new copy of the map, with the value at the key replaced with the result of passing the initial value to the update function.

It might help to see the literal version.

const todo = Map({ id: 0, text: 'foo', isDone: false });
todo.update('isDone', isDone => !isDone);
// => { id: 0, text: 'foo', isDone: true }

Connecting Everything

Now we’ve got our actions and reducer ready, we can create a store and connect it to our React components.

// src/app.js

import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { TodoList } from './components';
import reducer from './reducer';

const store = createStore(reducer);

render(
  <TodoList todos={store.getState()} />,
  document.getElementById('app')
);

We’ll need to make our components aware of this store. We’ll use the react-redux to help simplify this process. It allows us to create store-aware containers that wrap around our components, so that we don’t have to change our original implementations.

We’re going to need a container around our <TodoList /> component. Let’s see what this looks like.

// src/containers.js

import { connect } from 'react-redux';
import * as components from './components';
import { addTodo, toggleTodo } from './actions';

export const TodoList = connect(
  function mapStateToProps(state) {
    // ...
  },
  function mapDispatchToProps(dispatch) {
    // ...
  }
)(components.TodoList);

We create containers with the connect function. When we call connect() we pass two functions, mapStateToProps() and mapDispatchToProps().

The mapStateToProps function takes the store’s current state as an argument (in our case a list of todos), then it expects the return value to be an object that describes a mapping from that state to props for our wrapped component.

function mapStateToProps(state) {
  return { todos: state };
}

It might help to visualize this on an instance of the wrapped React component.

<TodoList todos={state} />

We’ll also need to supply a mapDispatchToProps function, which is passed the store’s dispatch method, so that we can use it to dispatch the actions from our action creators.

function mapDispatchToProps(dispatch) {
  return {
    addTodo: text => dispatch(addTodo(text)),
    toggleTodo: id => dispatch(toggleTodo(id))
  };
}

Again, it might help to visualize all these props together on an instance of our wrapped React component.

<TodoList todos={state}
          addTodo={text => dispatch(addTodo(text))}
          toggleTodo={id => dispatch(toggleTodo(id))} />

Now that we’ve mapped our component knows about the action creators, we can call them from event listeners.

export function TodoList(props) {
  const { todos, toggleTodo, addTodo } = props;

  const onSubmit = (event) => {
    const input = event.target;
    const text = input.value;
    const isEnterKey = (event.which == 13);
    const isLongEnough = text.length > 0;

    if(isEnterKey && isLongEnough) {
      input.value = '';
      addTodo(text);
    }
  };

  const toggleClick = id => event => toggleTodo(id);

  return (
    <div className='todo'>
      <input type='text'
             className='todo__entry'
             placeholder='Add todo'
             onKeyDown={onSubmit} />
      <ul className='todo__list'>
        {todos.map(t => (
          <li key={t.get('id')}
              className='todo__item'
              onClick={toggleClick(t.get('id'))}>
            <Todo todo={t.toJS()} />
          </li>
        ))}
      </ul>
    </div>
  );
}

The containers will automatically subscribe to changes in the store and they will re-render the wrapped components whenever their mapped props change.

Finally, we need to make the containers aware of the store, using the <Provider /> component.

// src/app.js

import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './reducer';
import { TodoList } from './containers';
//                          ^^^^^^^^^^

const store = createStore(reducer);

render(
  <Provider store={store}>
    <TodoList />
  </Provider>,
  document.getElementById('app')
);

Conclusion

There’s no denying that the ecosystem around React and Redux can be quite complex and intimidating for beginners, but the good news is that almost all of these concepts are transferable. We’ve barely touched the surface of Redux’s architecture, but already we’ve seen enough to help us start learning about The Elm Architecture, or pick up a ClojureScript library like Om or Re-frame. Likewise, we’ve only seen a fragment of the possibilities with immutable data, but now we’re better equipped to start learning a language like Clojure or Haskell.

Whether you’re just exploring the state of web application development, or you spend all day writing JavaScript; experience with action based architectures and immutable data is already becoming a vital skill for developers and right now is a great time to be learning the essentials.

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

  • thecommoner

    Thanks for the insight but please don’t recommend looking at tools such as Webpack that has over 450 issues. For a ToDo app this would be fast but I feel the trade off of having JS doing so much is a bit alarming to me. We have ran into a lot of performance issues with Large Web application using React, Redux. Also Having our developers needing to “compile” every time they make changes became a bottleneck especially when debugging. On top of that we saw significant issues in the JS profiler (Where JS is just doing to much and believe me we gradually checked every component piece by piece noticing that it’s just the frameworks already costing a lot, which leaves us with not a lot of room to spend. We ended up going back to barebones and stuck with Backbone, RequireJS (Only bundle and compile when we go to production) for building out components we included FastDom and used native JS selectors are App performed 300% faster and our development workflow increased, it also ensured our junior devs got to know JS more at the core including HTML.

  • Andy Grant

    When I read things like…

    “webpack is the new generation module bundler that replaces all the front-end building tools such as Grunt or Gulp.”

    …I get very put off and credibility takes a node dive.

    Webpack is an alternative to Browserify, but in no way a replacement for Gulp. Webpack is merely a subset of what Gulp is (you might as well have said Excel is replacing Word for all the sense it makes)

    Gulp = build tool which can do EVERYTHING

    Browserify = bundling

    Webapck = bundling (some other features, like
    processing sass)

    Oh yeah, Webpack is a pain in the ass and rarely works without issues – talk about rife with bugs the very instant you need it to do something a little complicated. Still, a lot of people use it within Gulp (yes, within, not instead of, becuase they are NOT the same kind of tool!) for typical build tasks.

  • boubiyeah

    Yeah… Webpack is overhyped in the React ecosystem (That tool kind of work but is quite bugged, has the shittiest API I’ve ever seen and the documentation is incredibly outdated; still, open source and all); browserify is still much simpler and productive if you don’t care about bundling styles at all.

    Redux and Immutable-js is an interesting combination and I could see it work quite well; Your articles code examples are deceptively simple though; For instance you make it look like you could simply map an immutable-js List and feed it to React when in fact the transformation back to the JS native equivalent (Array) is mandatory on every single render.

  • http://www.erwan-datin.com Erwan Datin

    Thank you for this article!

    I see a discussion on webpack :
    I’m not a big of webpack. Not because it does not the job but because too much pain for so little things.

    Consider JSPM alternative.
    It is so easy compared to webpack : http://egorsmirnov.me/2015/10/11/react-and-es6-part5.html.

    I created an repo to illustrate how React is ok with JSPM : https://github.com/MacKentoch/react-es6-jspm-starter

    For SASS or LESS, I used to use gulp.

  • http://www.phpadvocate.com jonathon hibbard

    Great Article. Well written and full of great examples/use cases for anyone who has ever worked with ReactJS. As for all the Java/.NET “developers” who are complaining about webpack being stupid/buggy or how this is all “deceptively easy” – lolz, troll on brothers!

    For a glimpse at your future, be sure to read the thread over on the Adobe Forums entitled, “Is ColdFusion dead, six feet under, pushing up daisies?” =) just saying…

  • http://www.phpadvocate.com jonathon hibbard

    Great Article. Well written and full of great examples/use cases for anyone who has ever worked with ReactJS. As for all the Java/.NET “developers” who are complaining about webpack being stupid/buggy or how this is all “deceptively easy” – lolz, troll on brothers!

    For a glimpse at your future, be sure to read the thread over on the Adobe Forums entitled, “Is ColdFusion dead, six feet under, pushing up daisies?” =) just saying…

  • http://www.phpadvocate.com jonathon hibbard

    Great article – well written with some good examples. I found the rants and ridiculous inaccuracies of the naysayers comments here extremely comical and a bit – well, confusing too. =

    “Oh yeah, Webpack is a pain in the ass and rarely works without issues” – i’d be curious about what all of these “issues” are – I personally have never run into any of them. Oo

    “I’m not a big of webpack. Not because it does not the job but because too much pain for so little things.” – um.. i *think* this person is saying there are lots of issues too. either way, same response as above.

    “Yeah… Webpack is overhyped in the React ecosystem…. has the shittiest API…documentation is outdated” – Granted, their API is not exactly one on a beginner-level…but then again, it was pretty easy to me. Difficulty aside, you’re probably gonna end up spending an hour or 2 at the most with it and then that’s probably it. If I had to spend more than that on it – ok, we’d have something to talk about…maybe. Oo

    “Thanks for the insight but please don’t recommend looking at tools such as Webpack that has over 450 (92 Bugs) issues.” – That’s a good point. They do need more help from the community maintaining it. Maybe we should help them out?

  • http://www.phpadvocate.com jonathon hibbard

    Great article – well written with some good examples. I found the rants and ridiculous inaccuracies of the naysayers comments here extremely comical and a bit – well, confusing too. =

    “Oh yeah, Webpack is a pain in the ass and rarely works without issues” – i’d be curious about what all of these “issues” are – I personally have never run into any of them. Oo

    “I’m not a big of webpack. Not because it does not the job but because too much pain for so little things.” – um.. i *think* this person is saying there are lots of issues too. either way, same response as above.

    “Yeah… Webpack is overhyped in the React ecosystem…. has the shittiest API…documentation is outdated” – Granted, their API is not exactly one on a beginner-level…but then again, it was pretty easy to me. Difficulty aside, you’re probably gonna end up spending an hour or 2 at the most with it and then that’s probably it. If I had to spend more than that on it – ok, we’d have something to talk about…maybe. Oo

    “Thanks for the insight but please don’t recommend looking at tools such as Webpack that has over 450 (92 Bugs) issues.” – That’s a good point. They do need more help from the community maintaining it. Maybe we should help them out?

  • http://blog.fritx.me Fritz Lin

    nice post!

  • Andy Grant

    @infolock, if you find the comments comical and confusing, its possible you simply dont relate to them.

    When the author said “webpack is the new generation module bundler that replaces all the front-end building tools such as Grunt or Gulp”, it revealed a fundamental misunderstanding of what webpack is and revealed the flaw in his assertion that it had (or even could) replaced other tools.

    We all make mistakes, and correcting them isn’t saying we dont appreciate the effort and work on the
    article – it was only the webpack bit he got wrong (or misinformed on).

    I suspect your confusion may be similar to what the authors was – as to it being comical, cant really say, maybe words like ‘grunt’ and ‘gulp’ make you giggle.

  • bgrz

    This is a horribly written tutorial, it’s impossible to follow.

    The boilerplate project was mentioned twice, so OK, that’s clear. Next come up the incomplete code snippets that refer to files not found in the boilerplate.

    OK, re-read from the beginning, find that there’s another project with complete source, continue following the tutorial by juggling through 6 tabs trying to cross-reference snippets with code that looks similar (but differs) to the one in the tutorial.

    Get to the part with TodoItem and TodoList – those can’t even be found in any of the two project sources. OK, I’ll just keep on and I’ll figure it out. Let’s see, what’s next, `ReactDOM.render( , ` SyntaxError ffffuuuuuuuuu

    • mildy_interested

      I agree. Is the wrong repo linked? actions/TodoActions.js and reducers/TodoStore.js are missing. That’s as far as I’m going.

  • fatlinesofcode

    The source code doesnt match the tutorials. E.g. there is no TodoApp or TodoList files. Everything is stuffed in HelloApp, which is pretty confusing.

  • A75Note

    Once I ran ‘npm start’ I got the following error: NODE_ENV is not recognized…
    Please advice

    • A75Note

      When its Windows change the ‘start’ in package.json to this:
      set NODE_ENV=development && node dev-server ./webpack/config

  • Kay2Dan

    Terrible tutorial. I have to agree with the negative comments above, I am surprised how anyone followed along with the tutorial.

  • James Hibbard

    @tomjleo:disqus , @bgrz:disqus , @kay2dan:disqus and everyone else having trouble with this article: it seems we dropped the ball a bit here. An update to this article is on the way. Thanks for the feedback!

  • Cezar Luiz

    Great article! Very useful!

    • Dan Prince

      Thanks Cezar!

  • Marc Sch

    Have you considered using Record to maintain the ability to reference object members directly?

    • Dan Prince

      Sure, it’s a perfectly good solution, but generally I prefer Map to Record in the same way that I prefer JavaScript objects to classes. I don’t mind the syntactic indirection of `.get` too much either, but that’s just personal preference.

  • Thomas Hoadley

    Hey great article! I was just wondering how long it took you personally to get to grips with react/redux? Also, did you have a developers background before hand? As you mention I’m definitely finding it all very confusing despite having put considerable work into it! Also, were there any other resources/methods you could recommend for learning?

    Thanks again!

    • Dan Prince

      It didn’t take long for me to pick up either library, but I learned them in total isolation and I had a 5 year background in web development when I started. The ideas in React were pretty new for me, but I’d learned the patterns in Redux from ClojureScript and Elm, so it always felt pretty natural. The major hurdle was the react-redux bindings, which I still don’t find particularly intuitive.

      My advice would be to isolate each technology, learn how to use it by itself, then finally integrate the other bits.

      • Thomas Hoadley

        Thanks for the reply. Since posting this i’ve actually got a lot better understanding of them. No replacement for good old fashioned hard work!

  • Alex Chen

    Uncaught ReferenceError: React is not defined

    • Dan Prince

      Sounds like you haven’t required or imported React. Remember, it needs to be imported in any file where you use JSX.

  • Ragu Pathy

    You have used Stateless functions in this example, Please correct me if am wrong….

    • Dan Prince

      Yep. In fact, I only used stateless functions, because I’m keeping track of all my component state with Redux instead.

  • David Whitaker

    Looks like you just repeated the docs http://redux.js.org/docs/basics/UsageWithReact.html with some minor alterations. Aside, I find it quite crazy how many wont read the docs and look everywhere else.

    Though was looking for example for syntax on using classes with redux apposed to const react return function being I needed the lifecycle methods.

  • Laurel Anderson

    Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many nodes to be unnecessarily re-created, which can cause performance degradation and lost state in child components.

    • Dan Prince

      You’re misunderstanding what that advice means. That’s specifically for components with “ which defeats the whole point of using unique ids in the first place.

      If you generate the keys in advance and keep them associated with the model (which is immutable), then the keys are going to be stable, regardless of how they were generated. For cryptographic strength, we can do better than Math.random, but for a demo or a simple application it’s not going to give React any problems.

  • 朱琳

    this page help me alot, thank you very much!

  • Yuri Predborski

    I’m a complete newbie with react and I came here to make a simple todo app.
    Funny thing: this works on codepen, but it doesn’t work on my PC. Says I need to start HTTP server instead of loading a file.

    Also, code after this paragraph:
    Now that we’ve mapped our component knows about the action creators, we can call them from event listeners
    Is not directly tied to any file, and its confusing for a complete newbie to try to understand where it belongs.

    I will figure it out somehow (I always do), just give me some time xD

    Thanks for the article! Now I know a bit more about the tools I plan to use! Even though its just the tip of the tip of the iceberg…

    -edit-
    Copied files from the git repository (they looked the same, I looked line after line, every file) and it started working. Magic…
    Must’ve had a typo somewhere.

  • http://wayou.github.io/ Wayou Liu

    `npm run build` won’t run until I replace the `script` with `scripts`

    • Lucas Pereira Caixeta

      yeap, true!

    • Nilson Jacques

      Fixed!

  • http://archonic.com Joshua Mark

    By the end it sounds like you’re saying “now that you’ve been introduced to these concepts, you’re equipped to learn a better framework”. The more I learn React, the more I wonder why it’s so popular. This rudimentary todo app has half a GB of dependencies and almost a full MB of client JS. I’m wondering if you’re under the impression that React is a shoddy bandwagon?

  • Haru El Rovix

    Got ERROR in Cannot find module ‘babel-core’. It seems missing babel-core it the Setup. Should it be
    npm install –save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react ?

  • Kris T

    This was exceptionally useful, but I’m curious about one thing:

    const toggleClick = id => event => toggleTodo(id)

    What is this line doing? I would have thought it would have worked without the EVENT portion, but sure enough it breaks if I take that out. What is going on there?

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in JavaScript, once a week, for free.