Why React Hooks + Context API a better choice than React + Redux

Why React Hooks + Context API a better choice than React + Redux

In this React tutorial, I will introduce the React Context API for state management, and show you what makes React Hooks plus the Context API a better solution than React plus Redux

Redux introduces a lot of complexity to our codebase with the excessive amount of code it requires. At best, this makes it an imperfect solution for state management in React applications. And yet, far too many React developers default to Redux for state management without considering other alternatives.

In this article, I will introduce the React Context API for state management, and show you what makes React Hooks plus the Context API a better solution than Redux.

Why we need a state management tool

In typical React, the way to handle data between disconnected components is through prop drilling. Since there is no global state that components can access if, for instance, you want to pass data from a top-level component to a fifth-level component, you’ll have to pass the data as a prop on each level of the tree until you get to your desired component.

This results in writing a ton of extra code, and giving components properties that they will never use also affects their architectural design. In order to solve this problem, we needed a way to provide a global state that all components, no matter how deeply nested they are, could access.

By solving this, Redux, an open-source JavaScript library for managing application state, became the go-to solution for React developers.

How Redux works

The Redux documentation describes it as a predictable state container for JavaScript applications that helps us to write applications that behave consistently, run in different environments, and are easy to test.

One disadvantage of prop drilling is the need for writing a considerable amount of extra code in order to access data from a top-level component. With Redux, this disadvantage is felt even more as a lot of complexity comes with all its extra code required for setting up a global state for our application. Redux requires three main building parts to function: actions, reducers, and store.

Actions

These are objects that are used to send data to the Redux store. They typically have two properties: a type property for describing what the action does and a payload property that contains the information that should be changed in the app state.

// action.js
const reduxAction = payload => {
  return {
    type: 'action description',
    payload
  }
};

export default reduxAction;

The type is usually in all caps, with its words separated by underscores. For example, SIGNUP_USER or DELETE_USER_DATA.

Reducers

These are pure functions that implement the action behavior. They take the current application state, perform an action, and then return a new state:

const reducer = (state, action) => {
  const { type, payload } = action;
  switch(type){
    case "action type":
      return {
        ["action description"]: payload
      };
    default:
      return state;
  }
};

export default reducer;

Store

The store is where the application’s state is housed. There is only one store in any Redux application:

import { createStore } from 'redux'

const store = createStore(componentName);

Since our application can only have one Redux store, in order to create a single root reducer for all our components, we’ll need the [combineReducers](https://redux.js.org/api/combinereducers) method from Redux.

With this long process and considerable amount of code required to set up Redux, imagine what our codebase will look like when we have multiple components to work with. Even though Redux solves our state management problem, it is really time-consuming to use, has a difficult learning curve, and introduces a whole new layer of complexity to our application.

Fortunately, the React Context API solves this problem. When combined with React Hooks, we have a state management solution that is less time-consuming to set up, has an easy learning curve, and requires minimal code.

The React Context API

The new Context API came with React 16.3. Here’s how Context is explained in the React documentation:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

The React context API is React’s way of managing state in multiple components that are not directly connected.

To create a context, we’ll use the createContext method from React, which accepts a parameter for its default value:

import React from 'react';

const newContext = React.createContext({ color: 'black' });

The createContext method returns an object with a Provider and a Consumer component:

const { Provider, Consumer } = newContext;

The Provider component is what makes the state available to all child components, no matter how deeply nested they are within the component hierarchy. The Provider component receives a value prop. This is where we’ll pass our current value:

<Provider value={color: 'blue'}>
  {children}
</Provider>

The Consumer, as its name implies, consumes the data from the Provider without any need for prop drilling:

<Consumer>
  {value => <span>{value}</span>}}
</Consumer>

Without Hooks, the Context API might not seem like much when compared to Redux, but combined with the useReducer Hook, we have a solution that finally solves the state management problem.

What are Hooks in React?

Hooks are a type of function that enables the execution of custom code in a base code. In React, Hooks are special functions that allow us to “hook into” its core features.

React Hooks provide an alternative to writing class-based components by allowing us to easily handle state management from functional components.

The useContext Hook

If you noticed, when explaining the React Context API, we needed to wrap our content in a Consumer component and then pass a function as a child just so we could access (or consume) our state. This introduces unnecessary component nesting and increases the complexity of our code.

The useContext Hook makes things a lot nicer and straightforward. In order to access our state using it, all we need to do is call it with our created context as its argument:

const newContext = React.createContext({ color: 'black' });

const value = useContext(newContext);

console.log(value); // this will return { color: 'black' }

Now, instead of wrapping our content in a Consumer component, we can simply access our state through the value variable.

The useReducer Hook

The useReducer Hook came with React 16.7.0. Just like the reduce() method in JavaScript, the useReducer Hook receives two values as its argument — in this case, the current state and an action — and then returns a new state:

const [state, dispatch] = useReducer((state, action) => {
  const { type } = action;
  switch(action) {
    case 'action description':
      const newState = // do something with the action
      return newState;
    default:
      throw new Error()
  }
}, []);

In the above block, we’ve defined our state and a corresponding method, dispatch, handling it. When we call the dispatch method, the useReducer() Hook will perform an action based on the type that our method receives in its action argument:

...
return (
  <button onClick={() =>
    dispatch({ type: 'action type'})}>
  </button>
)
The useReducer Hook plus the Context API

Setting up our store

Now that we know how the Context API and the useReducer Hook work individually, let’s see what happens when we combine them in order to get the ideal global state management solution for our application. We’ll create our global state in a store.js file:

// store.js
import React, {createContext, useReducer} from 'react';

const initialState = {};
const store = createContext(initialState);
const { Provider } = store;

const StateProvider = ( { children } ) => {
  const [state, dispatch] = useReducer((state, action) => {
    switch(action.type) {
      case 'action description':
        const newState = // do something with the action
        return newState;
      default:
        throw new Error();
    };
  }, initialState);

  return <Provider value={{ state, dispatch }}>{children}</Provider>;
};

export { store, StateProvider }

In our store.js file, we used the createContext() method from React that we explained earlier to create a new context. Remember that the createContext() method returns an object with a Provider and Consumer component. This time, we’ll be using only the Provider component and then the useContext Hook when we need to access our state.

Notice how we used the useReducer Hook in our StateProvider. When we need to manipulate our state, we’ll call the dispatch method and pass in an object with the desired type as its argument.

In our StateProvider, we returned our Provider component with a value prop of state and dispatch from the useReducer Hook.

Accessing our state globally

In order to access our state globally, we’ll need to wrap our root <App/> component in our StoreProvider before rendering it in our ReactDOM.render() function:

// root index.js file
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { StateProvider } from './store.js';

const app = (
  <StateProvider>
    <App />
  </StateProvider>
);

ReactDOM.render(app, document.getElementById('root'));

Now, our store context can be accessed from any component in the component tree. To do this, we’ll import the useContext Hook from react and the store from our ./store.js file:

// exampleComponent.js
import React, { useContext } from 'react';
import { store } from './store.js';

const ExampleComponent = () => {
  const globalState = useContext(store);
  console.log(globalState); // this will return { color: red }
};

Adding and removing data from our state

We’ve seen how we can access our global state. In order to add and remove data from our state, we’ll need the dispatch method from our store context. We only need to call the dispatch method and pass in an object with type (the action description as defined in our StateProvider component) as its parameter:

// exampleComponent.js
import React, { useContext } from 'react';
import { store } from './store.js';

const ExampleComponent = () => {
  const globalState = useContext(store);
  const { dispatch } = globalState;

  dispatch({ type: 'action description' })
};
Conclusion

To a good extent, Redux works for state management in React applications and has a few advantages, but its verbosity makes it really difficult to pick up, and the ton of extra code needed to get it working in our application introduces a lot of unnecessary complexity.

On the other hand, with the useContext API and React Hooks, there is no need to install external libraries or add a bunch of files and folders in order to get our app working. This makes it a much simpler, more straightforward way to handle global state management in React applications.

React.js & Redux Crash Course for Beginners - Full Tutorial

React.js & Redux Crash Course for Beginners - Full Tutorial

In this React.js & Redux crash course you will learn what React.js is and the fundamentals such as components, state, props, JSX, events, etc. We will talk about what Redux is and build a React app from scratch and add all of the boilerplate for Redux and work with the store/state, actions, reducers and all of the other fundamentals of the Redux state manager.

React.js Crash Course for Beginners - Full Tutorial

In this crash course you will learn what React JS is and the fundamentals such as components, state, props, JSX, events, etc.

Redux Crash Course for Beginners - Full Tutorial

In this video we will talk about what Redux is and build a React app from scratch and add all of the boilerplate for Redux and work with the store/state, actions, reducers and all of the other fundamentals of the Redux state manager. The actual Redux implementation starts around 22:00.

Understanding React with Redux and Redux Thunk for Beginners

Understanding React with Redux and Redux Thunk for Beginners

Introduction

Redux, according to the offical docs, is a predictable state container for JavaScript apps written by Dan Abramov. It’s a lightweight implementation of Flux, which is another library for managing the state. Basically Redux took the ideas that Flux brought in, leaving out its complexity by “borrowing” things from Elm.

For starters, there are several key concepts to understand: store, actions / action creators, and reducers. The official documentation is pretty straightforward and also plenty of examples and nice analogies can be found on the internet.

Principles

Redux has three fundamental principles:

  • single source of truth

The whole state of the application is stored in an object tree (within a single store). Visualize the state as a “model”, but without setters. As a plus, a single state tree enables us to debug our application with ease.

  • state is read-only

In order to modify state in Redux, actions have to be dispatched. Actions are a plain JavaScript object that describe what changed, sending data from the application to the store.

An action will look like this:

{
      type: 'ACTION_TYPE',
      action_value: string
  }

  • changes are made with pure functions

In order to tie state and actions together, we write a function called a reducer that takes two parameters: the (soon to be previous) state and an action. This pure function applies the action to that state and returns the desired next state.

Example of a reducer:

 export function reducer(state = '', action) {
      switch (action.type) {
          case 'ACTION_TYPE':
              return action.action_value;
          default:
              return state;
      }
  } 

Important: Reducers do not store state, and they do not mutate state. You pass state to the reducer and the reducer will return state.

Tip: As a best practice, even though it’s possible to have a single reducer that manages the transformation done by every action, it is better to use reducer composition - breaking down the reducer into multiple, smaller reducers, each of them handling a specific slice of the application state.

How it works

When one action is dispatched to the store, the combined reducer catches the action and sends it to each of the smaller reducers. Each smaller reducer examines what action was passed and dictates if and how to modify that part of state. You will find an example of a combined reducer a bit later in the article.

After each smaller reducer produces its corresponding next state, an updated state object will be saved in the store. Because this is important, I’m mentioning again that the store is the single source of truth in our application. Therefore, when each action is run through the reducers, a new state is produced and saved in the store.

Besides all of this, Redux comes up with another concept, action creators, which are functions that return actions. These can be linked to React components and when interacting with your application, the action creators are invoked (for example in one of the lifecycle methods) and create new actions that get dispatched to the store.

 export function actionCreator(bool) {
        return {
            type: 'ACTION_TYPE',
            action_value: bool
        };
    }

Fetching data from an API

Now onto our application. All of the above code snippets were just examples. We will now dive into the important bits of the code of our app. Also, a github repo will be available at the end of the article, containing the entire app.

Our app will fetch (asynchronously) data that is retrieved by an API - assuming we already built and deployed a working API, how convenient :) - and then display the fetched data as nice as my UI design skills go (not too far).

TVmaze’s public API contains tonnes of data and we will fetch all the shows they have ever aired. Then, the app will display all the shows, toghether with their rating and premiere date.

Designing our state

In order for this application to work properly, our state needs to have 3 properties: isLoading, hasError and items. So we will have one action creator for each property and an extra action creator where we will fetch the data and call the other 3 action creators based on the status of our request to the API.

Action creators

Let’s have a look at the first 3 action creators

export function itemsHaveError(bool) {
        return {
            type: 'ITEMS_HAVE_ERROR',
            hasError: bool
        };
    }

    export function itemsAreLoading(bool) {
        return {
            type: 'ITEMS_ARE_LOADING',
            isLoading: bool
        };
    }

    export function itemsFetchDataSuccess(items) {
        return {
            type: 'ITEMS_FETCH_DATA_SUCCESS',
            items
        };
    }

The first 2 action creators will receive a bool as a parameter and they will return an object with that bool value and the corresponding type.

The last one will be called after the fetching was successful and will receive the fetched items as an parameter. This action creator will return an object with a property called items that will receive as value the array of items which were passed as an argument. Instead if items: items, we can just write items, using an ES6 syntactic sugar called property shorthand.

To visualize a bit what was described earlier, this is how it looks in Redux DevTools:

Out of the box, action creators can return just actions. That’s where Redux Thunk comes in handy. Thunk allows us to have action creators that return a function instead of an action and dispatch an action only in certain cases.

If it wasn’t for Redux Thunk, we would probably end up having just one action creator, something like this:

export function itemsFetchData(url) {
        const items = axios.get(url);

        return {
            type: 'ITEMS_FETCH_DATA',
            items
        };
    }

Obviously, it would be a lot harder in this scenario to know if the items are still loading or checking if we have an error.

Knowing these and using Redux Thunk, our action creator will be:

 export function itemsFetchData(url) {
        return (dispatch) => {
            dispatch(itemsAreLoading(true));

            axios.get(url)
                .then((response) => {
                    if (response.status !== 200) {
                        throw Error(response.statusText);
                    }

                    dispatch(itemsAreLoading(false));

                    return response;
                })
                .then((response) => dispatch(itemsFetchDataSuccess(response.data)))
                .catch(() => dispatch(itemsHaveError(true)));
        };
    }

Reducers

Now that we have our action creators in place, let’s start writing our reducers.

All reducers will be called when an action is dispatched. Because of this, we are returning the original state in each of our reducers. When the action type matches, the reducer does what it has to do and returns a new slice of state. If not, the reducer returns the original state back.

Each reducer takes 2 parameters: the (soon to be previous) slice of state and an action object:

export function itemsHaveError(state = false, action) {
        switch (action.type) {
            case 'ITEMS_HAVE_ERROR':
                return action.hasError;
            default:
                return state;
        }
    }

    export function itemsAreLoading(state = false, action) {
        switch (action.type) {
            case 'ITEMS_ARE_LOADING':
                return action.isLoading;
            default:
                return state;
        }
    }

    export function items(state = [], action) {
        switch (action.type) {
            case 'ITEMS_FETCH_DATA_SUCCESS':
                return action.items;
            default:
                return state;
        }
    }

Now that we have the reducers created, let’s combine them in our index.js from our reducers folder:

 import { combineReducers } from 'redux';
    import { items, itemsHaveError, itemsAreLoading } from './items';

    export default combineReducers({
        items,
        itemsHaveError,
        itemsAreLoading
    });

Creating the store

Don’t forget about including the Redux Thunk middleware in the configureStore.js:


 import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from '../reducers';

    export default function configureStore(initialState) {
        const composeEnhancers = 
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
                window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
                    // options like actionSanitizer, stateSanitizer
                }) : compose;

        const enhancer = composeEnhancers(
            applyMiddleware(thunk)
        );

        return createStore(
            rootReducer,
            initialState,
            enhancer
        );
    }

Using the store in our root index.js

 import React from 'react';
    import { render } from 'react-dom';
    import { Provider } from 'react-redux';
    import configureStore from './store/configureStore';

    import ItemList from './components/ItemList';

    const store = configureStore();

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

Writing our React component which shows the fetched data

Let’s start by talking about what we are importing here.

In order to work with Redux, we have to import connect from ‘react-redux’:

    import { connect } from 'react-redux';

Also, because we will fetch the data in this component, we will import our action creator that fetches data:

    import { itemsFetchData } from '../actions/items';

We are importing only this action creator, because this one also dispatches the other actions to the store.

Next step would be to map the state to the components’ props. For this, we will write a function that receives state and returns the props object.

const mapStateToProps = (state) => {
        return {
            items: state.items,
            hasError: state.itemsHaveError,
            isLoading: state.itemsAreLoading
        };
    };

When we have a new state, the props in our component will change according to our new state.

Also, we need to dispatch our imported action creator.

    const mapDispatchToProps = (dispatch) => {
        return {
            fetchData: (url) => dispatch(itemsFetchData(url))
        };
    };


With this one, we have access to our itemFetchData action creator through our props object. This way, we can call our action creator by doing this.props.fetchData(url);

Now, in order to make these methods actually do something, when we export our component, we have to pass these methods as arguments to connect. This connects our component to Redux.

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

Finally, we will call this action creator in the componentDidMount lifecycle method:

    this.props.fetchData('http://api.tvmaze.com/shows');


Side note: if you are wondering why are we calling the action creator in componentDidMount instead of other lifecycle methods, I have found a couple of good reasons here:

  • You can’t guarantee the AJAX request won’t resolve before the component mounts. If it did, that would mean that you’d be trying to setState on an unmounted component, which not only won’t work, but React will yell at you for. Doing AJAX in componentDidMount will guarantee that there’s a component to update.

  • Fiber, the next implementation of React’s reconciliation algorithm, will have the ability to start and stop rendering as needed for performance benefits. One of the trade-offs of this is that componentWillMount, the other lifecycle event where it might make sense to make an AJAX request, will be “non-deterministic”. What this means is that React may start calling componentWillMount at various times whenever it feels like it needs to. This would obviously be a bad formula for AJAX requests.

Besides this, we need some validations:

    if (this.props.hasError) {
        return <p>Sorry! There was an error loading the items</p>;
    }

    if (this.props.isLoading) {
        return <p>Loading ...</p>;
    }


And the actual iteration over our fetched data array:

 {this.props.items.map((item) => (
        // display data here
    ))}

In the end, our component will look like this:

 import React, { Component, PropTypes } from 'react';
    import { connect } from 'react-redux';
    import { ListGroup, ListGroupItem } from 'react-bootstrap';
    import { itemsFetchData } from '../actions/items';

    class ItemList extends Component {
        componentDidMount() {
            this.props.fetchData('http://api.tvmaze.com/shows');
        }

        render() {
            if (this.props.hasError) {
                return <p>Sorry! There was an error loading the items</p>;
            }

            if (this.props.isLoading) {
                return <p>Loading ...</p>;
            }

            return (
                <div style={setMargin}>
                    {this.props.items.map((item) => (
                        <div key={item.id}>
                                <ListGroup style={setDistanceBetweenItems}>
                                    <ListGroupItem href={item.officialSite} header={item.name}>
                                        Rating: {item.rating.average}
                                        <span className="pull-xs-right">Premiered: {item.premiered}</span>
                                    </ListGroupItem>
                                </ListGroup>
                        </div>
                    ))}
                </div>
            );
        }
    }

    ItemList.propTypes = {
        fetchData: PropTypes.func.isRequired,
        items: PropTypes.array.isRequired,
        hasError: PropTypes.bool.isRequired,
        isLoading: PropTypes.bool.isRequired
    };

    const mapStateToProps = (state) => {
        return {
            items: state.items,
            hasError: state.itemsHaveError,
            isLoading: state.itemsAreLoading
        };
    };

    const mapDispatchToProps = (dispatch) => {
        return {
            fetchData: (url) => dispatch(itemsFetchData(url))
        };
    };

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

And that was all !

Our app will look like this:

I wasn’t lying about my design skills, was I ? :)

Last words and other resources

We now have an app that is fetching data asynchronously from an API, using React for our UI and Redux for managing the state of our application. I think this is a good starting point for a personal / small project and also you get to work with new technologies.

This doesn’t mean that Redux is the solution for every problem we face when writing apps in React or that Redux is a a must-use in any Javascript written project, as Dan Abramov states in an interesting article.

Also worth noting, Facebook are preparing React Fiber, a reimplementation of React. They state that its goal is to make it more suitable for animations and gestures and that the key new feature will be incremental rendering which is:

  • the ability to split rendering work into chunks and spread it out over multiple frames

The differences React vs. Redux when created the exact same app

The differences React vs. Redux when created the exact same app

I created the exact same app with Redux and React. Here are the differences. A beginner’s guide to using Redux with React.

Because learning Redux can be hard — at first.

It can be a great feeling once you’ve managed to learn how to use React and can confidently built a few apps. You’re able to manage state and everything seems to be working fine. However, it’s quite possible that a time may arrive where you have to learn Redux.

This may be because the app that you’ve been working on has gotten larger and larger and you’ve found yourself passing state all over the place and need a better way of managing your data. Or it may just be because you’ve noticed that a ton of job adverts require knowledge of Redux as well as React. Either way, knowing how to use Redux is a great piece of knowledge to have, and you should therefore, owe it to yourself to learn it.

However, one thing that can make it difficult to pick up is the amount of additional code you have to wrap your head around in order to understand how it works. I personally also feel that the documentation out there, including the official Redux docs, show so many different approaches to using Redux, that Redux no longer feels approachable.

In a way this is kind of a good thing, because it encourages you to use Redux in the way that you see fit, rather than saying “this is exactly how you should use it, and if you don’t then you are a bad developer”. However, this nicety doesn’t arrive until you know what you’re doing with Redux, which until then, just makes it really hard to pick up.

So how are we going to learn Redux?

By building an app with React, and then building the exact same app with Redux!

Oh and by the way, when I say “building an app with Redux”, we are still using React — we’re simply managing the flow of data with Redux. But from here on, apart from some tangents along the way, when I refer to the app being built with Redux, I really mean being built with React AND Redux.

Anyway, this intro is already longer than I’d anticipated. So let’s start by having a quick look at how the two apps look:

React vs Redux. Not a battle as such, but rather a variation of approach to your app.

Because these are both React apps that have been created with create-react-app, their file structure is mostly identical, with the exception of a redux folder inside of the Redux app. Let’s take a look at the file structure now:

React on the left. Redux on the right.

Now given that these two apps achieve the exact same thing, you might be wondering why Redux requires so many additional files, and what purpose they serve.

The first thing you will notice is the large amount of initial boilerplate code needed in order to get Redux set up for your app. This is, in large part and the mental model it applies — Redux also has its roots in functional programming but let’s save this rabbit hole for another occasion. Anyway, this additional code can really pay off when building larger applications that have lots of data flowing in and out from various components, APIs etc. So it may seem like overkill for this simple To Do app at first, but just stick with us as we’re not trying to encourage you to use Redux for simple apps, but rather we’re using this on a simple app in order to make Redux a little easier to pick up and understand.

Before we deep dive into the code, let’s push ahead and explain Redux a little more first.

Redux handles the flow of data through three key principles:

1. Stores

Also known as a single source of truth, a store is basically just an object that you initialise with some state, then whenever we want to update it, we overwrite the store with a new version of it. You may already be using these same principles in your React applications anyway, as it is generally considered a best practice to recreate state rather than mutating it. To further clarify the difference here, if we had an array and we wanted to push a new item into it, we wouldn’t update our store by pushing a new item into the array, but rather we would overwrite the store with an updated version of it.

2. Reducers

So our store gets updated through something known as a reducer. These basically are the mechanisms by which we send our new versions of state. That may not make much sense just yet, so let’s elaborate a little. Let’s say we have our store object and it has an array that looks like this: list: [{‘id: 1, text: ‘clean the house’}]. If we had a function that adds new items into our array, our reducer will explain to our store how the new version of our store will look. So in the case of our list array, we would likely grab the contents of our list, spread it into a new list array through the ... syntax, along with the new item we want to add. Therefore, our reducer for adding new items may look something like this: list: [...list, newItem]. This is also what we mean here when we discussed how we create new copies of our state for the store, rather than pushing new items into existing parts of it.

3. Actions

Now in order for our reducers to know what new data to put into our state, they have access to a payload. This payload is sent to our reducer through something known as an action. An action is typically accessible within the components in our app — via props — just like any function we create. Because these actions are in our components, we can pass them parameters — these become the payloads.

So with that in mind,

We can think of Redux in the following way: Our app has access to actions. These actions carry data (or a payload as it generally tends to be called) from our app. Actions have a type that it shares with a Reducer. Whenever the action type gets triggered, it picks up the payload and tells our store how it should now look — by which we generally mean how should our data object look now that it has been updated.

There are other parts to this Redux mental model, such as action creators and action types, among others — but these additional elements are not required for our To Do app.

The Redux setup here may be a good starting point for you and you might decide to deviate from this as you begin to get more comfortable with Redux. With that in mind, although I initially stated that the Redux docs can be a bit overwhelming, reviewing all of the different approaches taken should be viewed as a source of inspiration when it comes to creating your own setup.

Adding Redux to a React app.

So we can create our React app in the same way that we would with Create React App. Afterwards, use yarn or npm to install the following two packages: redux and react-redux and then you are good to go! There is also a dev dependency called redux-devtools-extension which can be very helpful when it comes to ensuring that your Redux app is working in the way that you want it to. It is, however, optional, so don’t feel like you have to install it if you don’t want to.

We mentioned all of the boilerplate earlier. Let’s explain what it all does.

We will start this by looking at the root file of our app, which is main.js

main.js

Here we have five imports. The first two are for React, so we won’t bother discussing them, and the fifth import is simply our App component. The third and fourth imports are what we will focus on. The third import, Provider is basically a gateway into our Redux store (the thing we spoke about earlier). There is actually a bit more to how this works, as we need to pick which components we want to have access to our store, but we’ll discuss how that works a little later.

As you will see, we wrap our component with a component. One thing you will also notice from the screenshot above is that our Provider takes a store prop, which we pass in our store variable. You will see that our fourth import, configureStore is actually a function that we have imported, and then returned the output of it to our store variable, as such: const store = configureStore();.

Now this configureStore basically, as you may have guessed, is our store configuration. This includes the initial state we want to pass in. This is a file that we actually create ourselves, and we will review this in more detail later. So in short, our main.js file imports our store and wraps our root App component with it, thus providing access to it.

Unfortunately there is more boilerplate required, so let’s move one step up and look at the additional code in our root App component:

So we have another file with five imports. The first is React, the fourth is a React component and the fifth is a css file, so let’s not bother discussing those any further. Remember how I said that there was a bit more to how we provide our components with access to our store? Well this is where our second import, connect comes into play.

If you look at the bottom of the screenshot above, you will see that rather than exporting our App component, we export connect, which is basically a curried function. A curried function is basically a function that returns another function. What connect is doing here is basically taking the contents of mapStateToProps and mapDispatchToProps — both of which we will discuss shortly — then takes our App component and adds the contents of mapStateToProps and mapDispatchToProps to it, then finally returns our App component with the new functionality added. So that’s that, but what is the contents of those mapStateToProps and mapDispatchToProps things?

Well, mapStateToProps takes the state from our store and passes it down as a prop for our connectedApp component. In this case, we give it the key of list as it followed the naming convention we gave it inside of our store (more on that later). We didn’t need to follow this convention though and could have called it whatever we wanted to — either way, list is what we will be referring to in our app whenever we want to access that particular part of state. Now you will see that mapStateToProps is a function that takes state as a parameter. In this case, state is basically our store object (more on that later). But for your reference, if we were to put a console.log('store', store) inside of mapStateToProps like so:

This is what the output would be:

So with that in mind, we are basically just accessing certain parts of our store and we are attaching those parts to our App through props — in this case, we can see from the console that our state is an object called appReducer, which contains a list array inside of it. Therefore, we attach it to our App component by our mapStateToProps function returning an object with a key of list and a value of state.appReducer.list. This can all seem a bit foreign and verbose at first but hopefully that has helped to break down what is going on here.

So what about mapDispatchToProps? Well that takes us to the third import in our App.js file, which is appActions. This is another file that we create, which we will dive into later. For now, just know that mapDispatchToProps is a plain object that takes the actions we will create and passes them into our connected App component as props. In Redux terms, Dispatch refers to the dispatching of an action, which is basically a fancy way of saying that we are executing a function. So mapDispatchToProps is kind of like saying mapFunctionsToProps, or mapActionsToProps. However, the React docs refer to this as mapDispatchToProps, so we will stick to that naming convention here.

One thing I wanted to note here is that in a typical larger React application, our mapStateToProps function might have lots of different key/value pairs inside of the object that gets returned. This could also be coming from various different reducers for the store in your Redux app, as you can have access points for your store if required. The same also applies to mapDispatchToProps, in the sense that while our simple To Do app only has one file that looks after our actions — appActions — a larger app may have several files that look after actions specific to certain parts of your app. Your mapDispatchToProps file may pull in actions from various places and then pass them all down as props to your App component. Again, it’s really up to you how you choose to compose your application.

So we’ve looked at the main chunks of boilerplate that spill out from Redux into our root files, let’s now take a look at what is going on inside of our Redux folder, before finally taking a look at how we pull this all together inside of our React subcomponents (by this I mean anything that isn’t the root App.js component).

The Redux folder

There is a lot to unpack here. Before we start, let’s take another look at the file structure of our app:

We will tackle this in the same order as the files appear in the screenshot above:

Actions

actions/appActions.js

So if we remember from earlier, our appActions file is the file we imported into our App.js file. This contains the functions that carry the data (also known as payload) from our app. In the case of our To Do app, we need three pieces of functionality:

  1. Ability to hold input data
  2. Ability to add items
  3. Ability to delete items

Now the first functionality — ability to hold input data — is actually going to be handled locally inside of our ToDo component. We could have opted to handle this ‘the Redux way’, but I wanted to demonstrate that not everything has to be handled through Redux if — in your opinion — it doesn’t make sense to do so. In this case, I wanted to simply handle input data at the component level, whilst maintaining the actual To Do list with Redux at a central level. So let’s move onto the two other functionalities required: adding and deleting items.

These functions simply take a payload. For adding new To Do items, it is likely that the payload we need to pass is the new To Do item. Therefore, our function ends up looking like this:

appActions.js

We see here that the function takes a parameter that I have opted to call todo and returns an object that has a type and a payload. We assign the value of the todo param to the payload key. Now as you may have noticed from the screenshot above, the types here are actually variables that have been imported from our actionTypes folder — but more on action types in a moment.

We also have our redux_delete function which takes an id as its payload, in order for our accompanying reducer to know which To Do item to remove. Finally, we have an appActions object which takes our redux_add and redux_delete functions as key and values. This could have also been written as:

const appActions = {
    redux_add: redux_add,
    redux_delete: redux_delete
};

If that would have made more sense to you. I’d also like to know that all of the naming used here, such as appActions and the prefixing of our functions with redux_ is not a requirement, it was simply my own naming convention.

Action Types

actionTypes/index.js

Now you may recall from earlier that I mentioned that there is a way that our reducers and actions know how to interact with one another — this is through the use of types. Our reducers will also access these same action types. As you can see, these are simply variables that have a name that matches the string it is being assigned to.

This part isn’t entirely necessary and you could avoid creating this file and pattern altogether if you wish. We do this, however, as a Redux best practice because it provides a central place for all of our action types which reduces the number of places we have to go to update these if we ever need to. And given that our reducers will also be using these, we can be confident that the names are always correct as we are always pulling them in from one place. Speaking of reducers

Reducers

There’s two parts here: our appReducer, and our rootReducer. In larger apps, you may have lots of different reducers. These will all then get pulled into your rootReducer. In our case, we could have just handled this with one reducer, given the small size of our app. However, I’ve opted to keep two here as you’ll likely operate in this fashion. Oh, and the names here were my convention — you can call your reducers whatever you want.

Let’s take a look at our appReducer.

reducers/appReducer.js

The first thing we see is that we’re importing the same action types that we have been using for our actions. The next bit is our initialState variable which is our state. This is what we will be using to initialise our store with so that we have some initial state to begin with. You may choose in your own projects to have an empty object if you do not need any initial state to begin with — again, it’s down to whatever works for your project.

The next bit is our appReducer function that takes two parameters: the first is a state param which is the state we want to begin with. In our case, we use default parameters to default the first parameter to our initialState object. This stops us from having to pass anything in further down the line. The second param is our action. Now this appReducer function gets triggered every time one of the functions from our appActions.js file is triggered — we will later see how these are triggered, but for now just know that the functions ultimately end up inside of our ToDo.js file. Anyway, so each time one of these functions get triggered, our appReducer runs through a series of switch statements to find the one that matches the action.type that was passed in. To get an idea of what the data looks like that gets triggered, let’s console.log out our action, as such:

Now in our app, let’s say that we create a new To Do item by typing “take out the trash” in our input field and pressing the + button. When we do this, we see the following in the console:

Now besides the payload, we can see that our action has a type of "ADD_ITEM". This matches up with the ADD_ITEM variable that our switch statement has as one of its cases:

As there is a match, it executes this, which basically tells our store how its new state should be. In this case, we are telling our store that the state should now equal a list array which contains the previous contents of the list array, along with the new payload we passed in, which if we take another look at what was logged to the console:

Now remember, the action carries the payload — this part is handled by the actions we saw in appActions.js. Our reducers pick up actions and handle them based on whichever action.type matches.

Let’s now take a look at our rootReducer:

reducers/index.js

The first import we have is combineReducers. This is a Redux helper function which basically gathers all of your various reducers and turns them into an object which can then be passed to our createStore function in our store, which we will take a look at in a moment. The second import is our appReducer file that we created and discussed earlier.

As mentioned earlier, we didn’t really need this step as our app is fairly simple, but I’ve decided to keep this in for learning purposes.

Store

Let’s take a look as our configureStore.js file:

store/configureStore.js

The first import here is createStore which holds the complete state of your app. You can only have one store. You can have many reducers that have their own initialState though. It’s key to understand the difference here though which is basically that you can have many reducers that provide some form of state, but you can only have one store that pulls in all of the data from your reducers.

The second import here is our rootReducer, which we have covered earlier. You will see that create a simple function called configureStore that returns our createStore import as a function that takes in our rootReducer as its only parameter.

Again, this is something we could have skipped and simply created out store within our root index.js file. Instead, I have kept this here as there is a lot of configuration you may find yourself doing for your store. This can range from setting up middleware to enabling additional Redux dev tools. It’s quite typical for this to occur, but as it would have been overkill to go into all of this now, I’ve removed anything from configureStore that wasn’t required for this app.

Okay, so we now have everything set up in our Redux folder, and we’ve hooked up Redux to our index.js file and our root App.js component. Now what?

Triggering Redux functions in our app

We’re on the home stretch now. We’ve set everything up and our connected components have access to our store — via mapStateToProps and our actions via mapDispatchToProps— as props. We access these props just like we normally would in React, but for reference:

ToDo.js

These three props are the same ones we passed in: the list contains our state, while redux_add and redux_delete are our add and delete functions.

We then just use these where we need to. In our case, I have maintained the same functions that I used in our vanilla React app, with the exception of instead of updating state locally with some sort of setList() function from a useState hook, we call our redux_add or redux_delete functions with the required payloads. Let’s take a look:

Adding items

Deleting items

Let’s take our deleteItem function and step back through each step that leads to the update of state in our app.

redux_delete takes the ID from the To Do item that we want to remove.

If we take a look at our appActions.js file, we see that the ID we passed in becomes the value of our payload:

appActions.js

We then see in our appReducer.js file that whenever the DELETE_ITEM type is hit in our switch statement, it returns a new copy of our state that has the ID from the payload filtered out of it:

appReducer.js

As our new state is updated, the UI in our app updates.

And that’s Redux!

We’ve looked at how to add Redux to a React project, how to configure a store, how to create actions that carry data, and how to create reducers that update our store. We’ve also looked at our how to connect our app up to Redux in order to enable access to all of our components. I hope you have found this useful and have gained a better understanding of how an app might look with Redux. And if you have enjoyed reading this, be sure to show your love by leaving some claps — hint, you can leave up to 50!

But wait, where is the app that just used React? I thought this was a comparison piece?

The aim here was to create an app that functions in exactly the same way as an app built just with React. However, in writing this article, it became apparent that a lot of time needed to be spent explaining how Redux works, so it ended up becoming a bit of a beginner’s guide to Redux to demonstrate how you would go about adding Redux into an existing React app. The existing React app in question was already reviewed here: I created the exact same app created in React and Vue. Here are the differences. So please feel free to check it out so that you can review the differences yourself.

Github links to both apps:

React ToDo: here

Redux ToDo: here

If you’re interested in forking the styles used in this article and want to make your own equivalent piece, please feel free to do so!

Thank you so much !