Mimic Redux with React Context API and hooks
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 -
Store
context withReact.createContext
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.StoreProvider
component - a wrapper component which returns aProvider
with value prop ofstate
anddispatch
fromuseReducer
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 -
- at line 6, we added
useContext
hook to get thedispatch
function, - at line 16, we replaced the console log statement with the
addTodo
action fromActions.js
file.
src/components/TodoList/index.jsx
Okay, so let’s get a quick rundown of things we did above -
- at line 6, we used
useContext
hook to get the value ofstate
anddispatch
from Store context, - 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 -
- at line 8 and 14, we added our
editTag
andremoveTodo
actions to our Todo component, - at line 19, we have defined inline style object todoStyle that defines todo style based on the current todo tag.
- 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
And yeah here is the link for tutorial repo, if you missed something.
Conclusion
So there you have it -
- Create an initial state object.
- Create context with
const context = React.createContext(initialState)
- Use useReducer hook to get state and dispatch
const [state, dispatch] = React.useReducer(reducer, initialState)
- Create
reducer
function and actions. - Wrap
Provider
around parent component. - 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 :)