DEV Community

Nabeel529886
Nabeel529886

Posted on

Creating your own mini Redux using Context API and useReducer hook | React.js

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:

import { createContext } from 'react'

const initialState = { count: 0 }

const context = createContext(initialState)
Enter fullscreen mode Exit fullscreen mode

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:

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)

Enter fullscreen mode Exit fullscreen mode

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 to create a new react app

Run the following command:

yarn create vite todo-app --template react
Enter fullscreen mode Exit fullscreen mode

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.

After creating react app. Run the following commands:

cd todo-app
yarn 
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

import { createContext } from 'react'

const initialState = { todos: [] }

export const TodosContext = createContext(initialState)

Enter fullscreen mode Exit fullscreen mode

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:

// 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
Enter fullscreen mode Exit fullscreen mode

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

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

export const TodosReducer = (prevState, action) => {
  switch (action.type) {
    case "ADD_TODO": {
      return {
        ...prevState,
        todos: [...prevState.todos, action.payload]
      };  
    }
    default: {
      return prevState;
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

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:

import { useReducer } from 'react'
import { TodosReducer } from './TodosReducer.js'
Enter fullscreen mode Exit fullscreen mode

and inside the TodosContextProvider component update the following code:


// 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
Enter fullscreen mode Exit fullscreen mode

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


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;
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)

Visualizing Promises and Async/Await 🤯

async await

☝️ Check out this all-time classic DEV post