Creating your own mini Redux using Context API and useReducer hook | React.js
Tags: webdev, javascript, react, programming
Published: 2023-01-28 15:58:00

Functional components have undoubtedly made React a powerful tool to create web interfaces. 

In this article we will look at how we can create our own state management system like Redux with the help of Context API and useReducer hook in React.js

Before diving into the code, let us see what Context API and useReducer hook are?

---

## Context API
 
Context API provides a way to share the state with different components in a component tree without manually passing the props through every level.

In React, you can create context by using `createContext` method. It requires initial state as an argument.

for example:

```js
import { createContext } from 'react'

const initialState = { count: 0 }

const context = createContext(initialState)
```

---


## useReducer Hook

If you are familiar with state management systems like Redux you should be familiar with **Reducers**. Reducers are pure functions that takes previous state and an action and returns the new state

`useReducer` hook can imported from react and it takes two parameters, Reducer function and initial state, and it returns the dispatch function and new state  

for example: 

```js
import { useReducer } from 'react'

const ReducerFunction = (prevState, action) => {
// this function holds all the logic of how you want your state to be updated
}

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

```

Now that we know, what Context API and the useReducer hook is, let us use both and build a simple todo app.

First let's create a new react app, we will use **[Vite](https://vitejs.dev/guide/)** to create a new react app

Run the following command:

```
yarn create vite todo-app --template react
```


> Notice that I am using `yarn` as package installer, if you prefer package installer other than `yarn` make sure to read the docs for creating react app using [vite](https://vitejs.dev/guide/).

After creating react app. Run the following commands:

```
cd todo-app
yarn 
```
This will install all the dependencies.

After that, run `yarn dev` to start a local server

---

I am going to create two folders inside `src` folder, `components` folder and `context` folder.

In the `components` folder create a file named `AddTodo.jsx`

Inside `AddTodo.jsx`, create a simple input field and button to add todo

for example: 

```jsx
import { useState } from 'react'

const AddTodo = () => {
  const [todo, setTodo] = useState("")

  return (
  <div>
   <input 
    type="text" 
    value={todo} 
    onChange={(e) => setTodo(e.target.value) }
   />
   <button>ADD TODO</button>
  </div>
 )
}

export default AddTodo
```

Notice that I have also created a state to handle input field.

Now, create a new file named `TodosContext.jsx` inside the `context` folder

Inside `TodosContext.jsx` we will first create the context for our todos as follows:

```jsx
import { createContext } from 'react'

const initialState = { todos: [] }

export const TodosContext = createContext(initialState)

```

Next we will create a provider component that will provide the states to all the components wrapped around it

for example in the same `TodosContext.jsx` file: 

```jsx
// Prev code

const TodosContextProvider = ({children}) => {
  
   return (
    <TodosContext.Provider
      value={{
        todos: [] // empty for now, here the new state from useReducer hook will come
      }}
    >
      {children}
    </TodosContext.Provider>
  );
}

export default TodosContextProvider
```

Inside your `App.jsx` file update the code to the following:

```jsx
import AddTodo from "./components/AddTodo";
import TodosContextProvider from "./context/TodosContext";
import "./styles.css";

export default function App() {
  return (
    <TodosContextProvider>
      <div className="App">
        <AddTodo />
      </div>
    </TodosContextProvider>
  );
}
```
Now create a new file name `TodosReducer.js` inside the `context` folder.

In this file we will create the Reducer function that will be responsible for updating the state based on the action provided to it

Write the following code in `TodosReducer.js` file

```js
export const TodosReducer = (prevState, action) => {
  switch (action.type) {
    case "ADD_TODO": {
      return {
        ...prevState,
        todos: [...prevState.todos, action.payload]
      };  
    }
    default: {
      return prevState;
    }
  }
};
```
Notice that `action` object have two properties `action.type` that defines the action you want to perform on the state and `action.payload` that is optional and depends on how you want your state to be updated. In our case we are appending `action.payload` at the end of todos array, So this will be our new todo.

Now, head back to the `TodosContext.jsx` file and import the `TodosReducer` function and useReducer hook:

```jsx
import { useReducer } from 'react'
import { TodosReducer } from './TodosReducer.js'
```

and inside the `TodosContextProvider` component update the following code:

```jsx

// prev code

const TodosContextProvider = ({ children }) => {
  const [state, dispatch] = useReducer(TodosReducer, initialState)

  const addTodo = (todo) => {
     dispatch({ type: "ADD_TODO", payload: todo })
  }

  return (
    <TodosContext.Provider
      value={{
        todos: state.todos,
        addTodo 
      }}
    >
      {children}
    </TodosContext.Provider>
  );
}

export default TodosContextProvider
```
Create a new file name `Todos.jsx` inside components folder and add the following code:

```jsx

import { useContext } from "react";
import { TodosContext } from "../context/TodosContext";

const Todos = () => {
  const { todos } = useContext(TodosContext);

  return (
    <ul>
      {todos.map((todo, i) => {
        return (
          <div key={i}
            <li>{todo}</li>
          </div>
        );
      })}
    </ul>
  );
};

export default Todos;
```

Notice that we are using `useContext` hook from react, giving it the context we created earlier and it will return the todos state and addTodo function back to us. But in `Todos.jsx` we only need `todos` state so we destructured it from the returned object and map it as list items.

Now head back to the `AddTodo.jsx` file and add the following code:

```jsx
import { useState, useContext } from 'react'
import { TodosContext } from '../context/TodosContext'

const AddTodo = () => {
  const [todo, setTodo] = useState("")
  const { addTodo } = useContext(TodosContext)

  return (
  <div>
   <input 
    type="text" 
    value={todo} 
    onChange={(e) => setTodo(e.target.value) }
   />
   <button onClick={() => addTodo(todo)}>ADD TODO</button>
  </div>
 )
}

export default AddTodo
```

In this file we have used the context to get the addTodo method that will take a todo and will trigger a dispatch function with the action type of "ADD_TODO" which will then update the `todos` state and the new added todo will be reflected in the `Todos.jsx` file.

So that is how you use the Context API and useReducer hook to manage the states. Although it was a simple example but imagine a large application with hundreds of states. It will be very hard to manage all those state by using useState hook and passing those states through props to other components.

With the help of Context API and useReducer hook the state management in React became much easier without the need of external libraries like Redux.

Below is the codesandbox for code above:

{% embed https://codesandbox.io/embed/sharp-zhukovsky-b2hbsi?fontsize=14&hidenavigation=1&theme=dark %}