State Management in React using the Context API and Hooks

Redux vs Context API

State Management in React using the Context API and Hooks
Dipankar Barman's photo
Dipankar Barman

Published on Jul 20, 2020

5 min read

State management is the most important concept in React. There are several ways to manage state in React. The most common way of managing state is by passing props down the components (prop drilling).

But this way of managing state is not scalable if you are building a large or medium-sized application. So, here we are going to discuss how you can manage state globally using Context API with hooks.

Redux is a hugely popular library for State Management in React. However, if you are a beginner you may find it tough to get your head around with Redux. So you can go with Context API approach as an alternative.

Here we are going to build a Todo App using context API and hooks. Actually, we don’t need to manage state globally for this small app, but I want to give a brief overview of context API so that you can use it in a bigger project.

You can see the live demo here

We will be using Bootstrap for styling and font Awesome for icons. So make sure you import the CDN links in index.html file under the public folder.

The project structure of the end project looks something like this.

public
src
 |- components
    |- TodoForm.js
    |- TodoList.js
 |- contexts
    |- TodoContext.js
 |- App.css
 |- App.js
 |- index.js
README.md
package.json

Github link of the complete project here. Drop a star if you like it.

Let’s create our context first,

import React, { createContext, useState } from "react";
import uuid from "uuid/v1";

export const TodoContext = createContext({});

const TodoContextProvider = props => {
  const [todos, setTodos] = useState([]);

  const addTodo = todo => {
    setTodos([...todos, { todo, id: uuid() }]);
  };

  const removeTodo = id => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  const data ={ todos, addTodo, removeTodo };

  return (
    <TodoContext.Provider value={data}>
      {props.children}
    </TodoContext.Provider>
  );
};

export default TodoContextProvider;

As you can see, we create our context using createContext which we import from ‘react’.

export const TodoContext = createContext({});

We need to wrap it under a provider to make it available for other components to use. We are basically using two functions, addTodo and removeTodo for adding and removing todos which we will use in our Todo components.

const addTodo = todo => {    
      setTodos([...todos, { todo, id: uuid() }]);  
};
const removeTodo = id => {      
      setTodos(todos.filter(todo => todo.id !== id));  
};

You need to pass these functions as the value inside the provider like this.

const data = { todos, addTodo, removeTodo };

<TodoContext.Provider value={data}> 
     {props.children}    
</TodoContext.Provider>

So our TodoContext is done, but how will you use it? It’s not connected to any other components. How will other components know that this file exists?

You need to import this file in App.js and wrap it around it so that it can pass all these values down to other components.

import React from "react";
import "./App.css";
import TodoForm from "./components/TodoForm";
import TodoContextProvider from "./contexts/TodoContext";
import TodoList from "./components/TodoList";

function App() {
  return (
    <div>
      <TodoContextProvider>
        <TodoForm />
        <TodoList />
      </TodoContextProvider>
    </div>
  );
}

export default App;

As you can see TodoContextProvider is wrapping the other components. Don’t worry about TodoForm and TodoList, we will create these components now. All the values passed inside TodoContext.provider are now available to use inside TodoForm and TodoList component.

Let’s create the form to enter the todos in TodoForm component

import React, { useState, useContext, useEffect } from "react";
import "../App.css";
import { TodoContext } from "../contexts/TodoContext";

export default function TodoForm() {

  const { todos, addTodo } = useContext(TodoContext);

  const [todo, setTodo] = useState("");

  const handleSubmit = e => {
    e.preventDefault();
    addTodo(todo);
    setTodo("");
  };

  return (
    <div className="card card-body my-3 form">
      <h3 className="text-center text-info"> Todo List App in ReactJS</h3>
      <hr />
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          className="form-control"
          placeholder="Add your Todo"
          value={todo}
          required
          onChange={e => setTodo(e.target.value)}
        />
        <button className="btn btn-success btn-block mt-3">Add Todo</button>
      </form>
    </div>
  );
}

useContext is one of the built-in Hooks, giving functional components easy access to your context. We are accessing the addTodos function through useContext.

const { todos, addTodo } = useContext(TodoContext);

On submitting the button addTodo function is called and todo is passed as an argument.

const handleSubmit = e => {    
     e.preventDefault();    
     addTodo(todo);    
     setTodo("");  
};

For styling, we are mostly using bootstrap classes and some custom CSS which are written in App.css.

import React, { useContext } from "react";
import "../App.css";
import { TodoContext } from "../contexts/TodoContext";

export default function TodoList() {
  const { todos, removeTodo } = useContext(TodoContext);

  return todos.length ? (
    <div className="card card-body my-3 form">
      <h3 className="text-center text-info"> {todos.length} Things to do</h3>
      <hr />
      <ul className="list-group list-group-flush">
        {todos.map(todo => {
          return (
            <div
              className="row shadow-sm p-1 mb-3 bg-white rounded"
              key={todo.id}
            >
              <div className="col-md-8">
                <li className="list-group-item">
                  <i className="fa fa-check-square-o"></i> {todo.todo}
                </li>
              </div>
              <div className="col-md-2 text-center">
                <i
                  className="fa fa-trash-o delete"
                  onClick={() => removeTodo(todo.id)}
                ></i>
              </div>
            </div>
          );
        })}
      </ul>
    </div>
  ) : (
    <div className="card card-body my-3 form">
      <h4 className="text-center text-info" style={{ fontFamily: "Solway" }}>
        Nothing left to do. Enjoy your day off !
      </h4>
    </div>
  );
}

This component is responsible for listing the todos in the UI.Same as TodoForm component we are accessing todos and removeTodo using useContext.

const { todos, removeTodo } = useContext(TodoContext);

In removeTodo, todo id is passed which will be used to delete that particular todo. We are mapping all the todos and then displaying them in the UI.

@import url("https://fonts.googleapis.com/css?family=Roboto+Slab|Solway&display=swap");

.form {
  width: 60%;
  margin: auto;
  font-family: "Roboto Slab", serif;
}

.delete {
  font-size: 20px;
  color: rgb(18, 198, 230);
  cursor: pointer;
  margin-top: 15px;
}

That’s how we can manage global state using useContext. It’s a small application, but you can use useContext to handle state in any big application. It removes the dependency on Redux which is an external library and easy to use and understand than Redux.

Hope you find this article helpful. If yes, please do give a thumbs up and pen down your valuable feedback below. Thank you !!

Find me on LinkedIn Instagram

 
Share this