A beginner’s guide to Redux with React

The trickiest part of front-end development?

Published on
Jun 19, 2019

Read time
9 min read

Introduction

When it comes to learning front-end development, few tools cause more headaches than Redux. It’s become a standard technology in the repertoire of many React developers, but it’s arguably one of the trickiest parts. In case you need convincing, I collected several comments from people venting about their struggle to understand Redux on YouTube:

“I have thrown Redux in the dustbin as I can’t get my head around it.”

“This Redux stuff is too complex to understand and it’s still worse to remember all this.”

“Setting up Redux is one of the most disgusting things I have come across during my front end development journey… this really could be the ugliest part of the entire process.”

Fear not! It may seem fiddly at first but, like most tricky parts of web development, Redux becomes straightforward with practice.

I think one mistake that a lot of tutorials make is getting into the code too quickly before actually explaining the underlying concept. So, in this article, we’ll start by looking at what Redux is, the way it works, and common Redux file structures before actually jumping into some code and building a simple counter app.

I also think a lot of developers who want to learn Redux come to it knowing React, and they assume that — because they understand React’s state system — they’ll pick up Redux very quickly. While this may be the case for some, for most learners Redux may take some time to figure out. Be prepared to give it a bit of thought!

What is Redux?

Redux is a state container for JavaScript apps. It is most commonly paired with React, where it takes control of state away from React components and gives it to a centralised place called a ‘store’.

The diagram below represents the flow of props in a regular React app without Redux. Each circle represents a component.

When a component initiates a change (the blue circle), this change is communicated to the other components one step at a time. This may seem simple enough when we only have 10 components, but what about an app with 20, 50 or 100 components? As an app becomes larger, debugging can quickly become tricky, as we lose sight of how information is passed from one component to another.

And this diagram shows the same app with Redux. This time, when a component initiates a change, that information goes straight from it (the blue circle) to our store (the green circle). From there, the change is then communicated directly to all the components that need to update.

Redux, therefore, can make it easier to diagnose problems: a problem will either be in the component that initiated the change (the blue circle) or in the code related to Redux itself (the green circle).

Redux Flow

Before jumping into the code, it’s useful to think about how Redux is working conceptually. The diagram below demonstrates the essential steps to Redux’s process:

Step 1: UI (User Interface)

This is where a change is triggered. For example, a user clicking a ‘+’ button in a simple counter app.

Step 2: Actions

The actual action we want to take place, for example, “add one”.

In Redux, actions are plain JavaScript objects, and they must have a type property (e.g. 'ADD_ONE' ).

Step 3: Reducer

These specify how the application’s state should change in response to each action. For example, our new state should be one integer higher than our old state. (It is reducers which give Redux its name — they share the same Latin root).

Step 4A: Store

The store brings everything together. It holds application state, and it is where you will find three critical methods:

  • getState() — which allows access to the state object
  • dispatch(action) — which allows state to be updated
  • subscribe(listener) — which registers listeners, allowing code to trigger every time a change takes place

Step 4B: State

Finally, state is contained within the store. It is a concept you should be familiar with from React. In short, it is an object that represents the dynamic parts of the app: anything that may change on the client-side.

In our example of a counter app, the state object will contain whatever number our counter is on. This change is then communicated back to the UI, where it will appear to the user.

Additional Jargon

There are also a few more terms you’re likely to encounter when using Redux:

Boilerplate

Sections of code that have to be included in many places with little or no alteration. One of the reasons Redux can seem tricky to beginners is because it contains more boilerplate than you’re likely used to for front-end development.

Payload

The conventional name used for the property that holds the actual data in a Redux action object. A payload isn’t necessary, but it’s fairly common to see actions defined like this:

const ADD_USER = {
  type: "ADD_USER",
  payload: { name: "John Smith", age: 45 },
};

Middleware

In general, middleware glues together client-side and server-side code, allowing (back-end) developers to implement logic based upon the request made from the client. In Redux, middleware provides a way to interact with actions that have been dispatched to the store before they reach the store’s reducer.

File Structure

Finally, before diving into the code, we’ll take a quick look at file structure. The fact that Redux’s boilerplate code tends to span multiple files can be one of the most confusing parts for beginners. Redux is largely unopinionated, which means there are endless ways to structure a Redux app. One of the simplest options looks like this:

The image above shows the src folder, lightly adapted from src folder that comes with create-react-app. I’ve deleted any unnecessary files and added in three new files for Redux:

  • actions.js — for storing our app’s various actions and their definitions;
  • reducers.js — where we define the reducers for each action;
  • store.js — where we create our store and bring everything together.

In reality, few apps are likely to have file structures as simple as the one above. Below is a more scalable example.

Let’s say our app had a series of actions related to filtering items in a shop: some based on budget and others based on product category. Instead of putting these together in a single file, we can keep our code cleaner by organising them in separate files for each group.

So, for example, our actions/actionGroup1.js would correspond to reducers in our reducers/reducerGroup1.js. Meanwhile, actions/actionTypes.js and reducers/index.js bring all the different action- or reducer-groups together.

In our sample app, we’ll follow the simpler format, but it’s worth bearing in mind how you could scale up for a larger project.

Coding a Simple Counter App with Redux

We’ll begin with a simple example. Ensure you have Node.js installed, then type the following into your terminal:

npx create-react-app redux-counter

To set up our (extremely basic) UI, create a file called src/Counter.js and paste in the following code:

import React, { Component } from "react";

const containerStyle = {
  display: "flex",
};

const buttonStyle = {
  fontSize: "1.5rem",
  width: "40px",
  height: "40px",
};

class Counter extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <h1>0</h1>
          <div style={containerStyle}>
            <button type="button" style={buttonStyle}>
              -
            </button>
            <button type="button" style={buttonStyle}>
              +
            </button>
          </div>
        </header>
      </div>
    );
  }
}

export default Counter;

Next, let’s pass our counter into src/App.js:

import React from "react";
import "./App.css";
import Counter from "./Counter";

function App() {
  return (
    <>
      <Counter />
    </>
  );
}

export default App;

When that’s done, you should see something like this:

Now, if we were using React’s component state to provide the counter’s functionality, we could make src/Counter.js look something like this:

import React, { Component } from "react";
import "./App.css";

const containerStyle = {
  display: "flex",
};

const buttonStyle = {
  fontSize: "1.5rem",
  width: "40px",
  height: "40px",
};

class Counter extends Component {
  state = {
    number: 0,
  };

  addOne = () => {
    this.setState({
      number: this.state.number + 1,
    });
  };

  minusOne = () => {
    this.setState({
      number: this.state.number - 1,
    });
  };

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <h1>{this.state.number}</h1>
          <div style={containerStyle}>
            <button type="button" style={buttonStyle}>
              -
            </button>
            <button type="button" style={buttonStyle}>
              +
            </button>
          </div>
        </header>
      </div>
    );
  }
}

export default Counter;

The question is: how do we achieve the same functionality using Redux?

In the remainder of this article, we’ll look at one way of doing it. This is, of course, a simple example, and so all the code could go into a single file. However, to help distinguish the different parts of Redux, I’ll separate the main functionalities into different files corresponding to their steps in the ‘Redux flow’ above.

actions.js

We’ll start by defining our actions. An action can be an object or, as in our case, a simple string. We need one action to increment our counter, and one action to decrement it, so create a file called actions.js and add the following:

export const ADD_ONE = "ADD_ONE";
export const MINUS_ONE = "MINUS_ONE";

Simple!

reducers.js

Next, we need to specify how the application’s state should change in response to each action.

This is where we can record our initial state, which is an object just like what you’d expect in a regular React state. Create a file called reducers.js and add in the following:

const initialState = {
  number: 1,
};

Next, we need to create our reducer function. This takes two arguments:

  • a state;
  • an action, which defines how we go about changing state.

It’s common to pass in our initial state as the default argument, and this is also where we import our actions. The final code for reducers.js should look like this:

import { ADD_ONE, MINUS_ONE } from "./actions";

const initialState = {
  number: 0,
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case ADD_ONE:
      return {
        number: state.number + 1,
      };
    case MINUS_ONE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
}

export default reducer;

Note that it’s common to use switch statements to distinguish action types here, but regular if and else statements will work fine too.

store.js

Up til now, we haven’t needed to install the redux and react-redux dependencies, but we’ll need them to set up our store. Type the following into your terminal:

npm i redux react-redux

When that’s done, we’ll need to import the createStore function from 'redux' and pass in our reducer, like so:

import { createStore } from "redux";
import reducer from "./reducers";

const store = createStore(reducer);

export default store;

App.js

Now that we’ve exported our store we need to import it into App.js, by passing it into React-Redux’s Provider component:

import React from "react";
import { Provider } from "react-redux";
import Counter from "./Counter";
import store from "./store";
import "./App.css";

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;

This means that any child of the Provider component can access our store and, therefore, our actions and reducers.

Counter.js

Finally, we need to connect our Counter component to our store, which we can do by importing connect (which is a higher order function) from the react-redux module:

import { connect } from "react-redux";

We should also create a helper function mapStateToProps, which will allow us to access number from our Redux state as a prop inside our component.

const mapStateToProps = (state) => {
  return {
    number: state.number,
  };
};

At the bottom of Counter.js, we need to wrap our component in the connect function, passing in mapStateToProps. You should be used to this syntax if you’ve worked with higher order functions before:

export default connect(mapStateToProps)(Counter);

The connect function must be used to export any component that needs to access to change Redux state.

Lastly, we can use the dispatch prop (by calling this.props.dispatch) to link each method of our component with an action:

addOne = () => {
  this.props.dispatch({ type: "ADD_ONE" });
};

minusOne = () => {
  this.props.dispatch({ type: "MINUS_ONE" });
};

And that’s everything. The overall code in Counter.js should look something like this:

import React, { Component } from "react";
import { connect } from "react-redux";

const containerStyle = {
  display: "flex",
};

const buttonStyle = {
  fontSize: "1.5rem",
  width: "40px",
  height: "40px",
};

class Counter extends Component {
  addOne = () => {
    this.props.dispatch({ type: "ADD_ONE" });
  };

  minusOne = () => {
    this.props.dispatch({ type: "MINUS_ONE" });
  };

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <h1>{this.props.number}</h1>
          <div style={containerStyle}>
            <button onClick={this.minusOne} type="button" style={buttonStyle}>
              -
            </button>
            <button onClick={this.addOne} type="button" style={buttonStyle}>
              +
            </button>
          </div>
        </header>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    number: state.number,
  };
};

export default connect(mapStateToProps)(Counter);

If you’ve done everything correctly, your counter should now be working!

Adding Redux DevTools

A final useful step to working with Redux is to install and initiate Redux’s own development tools.

If you’re using Chrome, you can enable Redux DevTools by following the link below:

Redux DevTools
https://chrome.google.com/webstore/detail/redux-devtools/lmh...

But there’s one more step to getting it to work. In our createStore method, we need to pass the following as an argument after our reducer:

window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()();

With that addition, store.js should look like this:

import { createStore } from "redux";
import reducer from "./reducers";

const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

export default store;

We’ll now be able to access Redux DevTools from Chrome’s DevTools panel and selecting Redux.

To see a working version of our counter app, check out this repository.

Overall, I hope this article’s made Redux a little less intimidating.

Of course, a simple counter app is just the start of what you can do with Redux. If you’d like to take your knowledge further, below are a few resources I recommend for those who interested in learning more.

Resources

1. The official Redux tutorial:

Basic Tutorial: Intro
https://redux.js.org/basics/basic-tutorial

2. A video series by Dan Abramov, the creator of Redux:

Getting Started with Redux
https://egghead.io/courses/getting-started-with-redux

3. A crash course on Redux by one of my favourite YouTube developers (this is where I found the comments listed at the beginning of the article):

Thanks for reading and happy coding!

© 2024 Bret Cameron