Mimic Redux with React Context API and hooks

Sumit Joshi
The Startup
Published in
6 min readJul 22, 2019

If you have worked on a React project you should know how sometimes state management can be a real disaster because of prop-drilling. And Redux is usually the most popular way to solve it with its global state management. But with the recent updates in React, it is now possible to manage global state in React itself without using a third party framework like Redux.

In this article, we will learn to mimic the Redux global state management with React hooks and Context API. But first, let’s check all the prerequisites.

Prerequisites

You need to have at least understanding of-

  • basics of Redux - dispatcher, actions, reducers, etc
  • React, as we are having a React tutorial ;)
  • basic React hooks - useState

A bit about hooks and Context API

Note: If you are familiar with Context API and useReducer hook you can skip to next part.

Context API

Context API provides a way to pass data across the components without having to pass props down manually at every level.

You can create a context object with React.createContext -

const MyContext = React.createContext(defaultValue)

When a component subscribes to this context it will read the current value of context.

Every context object comes with a Provider and Consumer Component —

  • Provider allows consuming components to subscribe to context changes.
<MyContext.Provider value={/* some value */}>
{/* children */}
</MyContext.Provider>
  • Consumer allows consuming the value store from the Context Provider.
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>

useContext

useContext is a React hook that returns the current value of context object defined in useContext hook.

const value = useContext(MyContext)

Here the current context value is determined by the value prop of the nearest <MyContext.Provider> above the calling component.

useReducer

useReducer is an alternative of useState. It is usually preferable to useState when state logic is either complex or when the next state depends on the previous one.

const [state, dispatch] = useReducer(reducer, initialState)

It accepts a reducer function of type (state, action) => newState and returns the current state paired with a dispatch method. It is similar to Redux, so if you are familiar with it, you already know how it works.

Now we are done with the basic introduction to Context API and hooks that we will be using in this article, so let’s dive into a practical example to understand how it works together.

A Practical Example — TODO app

What we are building

We are going to build a basic to-do app where you can add/remove a todo and tag a todo as completed.

In this tutorial, we will first define our state with useReducer hook and then lift it with Context API.

Getting Started

Let’s start with creating a React project. We can do this using create-react-app or some other boilerplate of choice. Here, I will be using create-react-app -

create-react-app todo-app-global-state

Once your project is created, delete unnecessary files - logo.svg, App.test.js and rename App.js to App.jsx.

Now let’s start with creating our todo components. First, create a components folder inside src folder -

cd src
mkdir components && cd components

Then, create TodoForm and TodoList components inside components folder-

mkdir TodoForm && touch TodoForm/index.jsx
mkdir TodoList && touch TodoList/index.jsx

If the file structure seems confusing, you can check it below -

...
/src/
|-- /components/
|-- /TodoForm/
|--index.jsx
|-- /TodoList/
|--index.jsx
|-- App.jsx
|-- App.css
|-- index.js
|-- index.css
...

src/components/TodoForm/index.jsx

src/components/TodoList/index.jsx

Now add these components to App.jsx -

src/App.jsx

With this, our basic app setup is completed and we can move to our main topic setting up a global store.

Create a Store

Let’s start with creating a store folder which will have our store and actions inside src folder -

cd src
mkdir store && cd store

Create a Store.jsx file inside store folder -

touch Store.jsx

Here in Store.jsx we will first create a Store context with React.createContext, then we will work on our reducer function and define our Store Provider component.

src/store/Store.jsx

Let’s get a quick rundown of things in the above code. We created a -

  1. Store context with React.createContext
  2. reducer function - a pure function which takes current state and action object as params and returns the new state after doing calculations on current state based on the action received.
  3. StoreProvider component - a wrapper component which returns a Provider with value prop of state and dispatch from useReducer hook.

The next step is to open index.js in src folder and wrap App component in StoreProvider -

With this, our App component and all other components in the component tree can access Store context.

Add cases for Reducer

The next step is to add cases to the reducer function.

So let’s start with populating the initialState with a todos array -

const initialState = {
todos: []
}

And adding cases to the reducer function -

src/store/Store.jsx

Here we added cases for adding, removing a todo from the todo list and changing the current todo tag - active/complete.

Create our Actions

With the reducer function completed, actions are the final part to complete our React global state.

Create Actions.js in store folder inside src folder -

touch Actions.js

src/store/Actions.js

Connect Global State with Components

The next step is to connect our global react to React components so that we can access global state from components.

Here we will use useContext hook to get the value of Store context we created earlier.

So let’s open TodoForm and TodoList component and edit them -

src/components/TodoForm/index.jsx

So here we didn’t change a lot of things, but let’s take a quick look -

  1. at line 6, we added useContext hook to get the dispatch function,
  2. at line 16, we replaced the console log statement with the addTodo action from Actions.js file.

src/components/TodoList/index.jsx

Okay, so let’s get a quick rundown of things we did above -

  1. at line 6, we used useContext hook to get the value of state and dispatch from Store context,
  2. at line 8, we created an array - todoList that map todos array to the Todo component.

But we do not have a Todo component yet so, let’s create a Todo component -

cd TodoList
touch Todo.jsx

src/components/TodoList/Todo.jsx

Now let’s take a quick view through the Todo component code -

  1. at line 8 and 14, we added our editTag and removeTodoactions to our Todo component,
  2. at line 19, we have defined inline style object todoStyle that defines todo style based on the current todo tag.
  3. Additionally, we wrapped our Todo component in React.memo.

React.memo is a higher-order component (hoc). It’s similar to React.PureComponent but for function components instead of classes. So what it does is that wrapping a component inside React.memo will not re-render if input props are the same. But it only works if props are not functions, if you are passing functions as props you should use useCallback hook in addition to React.memo.

Some Styling

Now with everything done, let’s add a bit of styling to our app.

Edit index.css and App.css -

src/index.css

src/App.css

With this, we have created our todo app using React global state. And what’s left is just to launch the project with -

yarn start 
Todo App

And yeah here is the link for tutorial repo, if you missed something.

Conclusion

So there you have it -

  1. Create an initial state object.
  2. Create context with const context = React.createContext(initialState)
  3. Use useReducer hook to get state and dispatch const [state, dispatch] = React.useReducer(reducer, initialState)
  4. Create reducer function and actions.
  5. Wrap Provider around parent component.
  6. Consume context with const {state, dispatch} = React.useContext(context)

But now the main question arises - can React global state replace Redux completely? And the answer is no for now, as alongside global state Redux also provides a bunch of other features — Redux dev tools, middlewares, ability to combine multiple reducers (no official way to combine React reducers yet) are some of the things I can think of.

So for your next project, you might need to think about whether you need Redux or, React global state is the right tool for you.

And lastly, let me know if I got something wrong or if there is something that could be improved or simplified.

Thanks for reading and happy coding :)

--

--