DEV Community

Cover image for Create a react app using context, hooks and reducers
Majid Kareem
Majid Kareem

Posted on • Updated on

Create a react app using context, hooks and reducers

Yes, I know what you're thinking. This isn't just another todo list tutorial, is it?
In this post, I'll be talking about a little advanced concept called React Context and how to use it in an application by building a very basic todo list app.

Expectations

This post assumes a basic understanding of the react framework.

Let's get started


What is Context?

According to official react documentation

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Why/When do we need to use Context?

React documentation to the rescue again

In a typical React application, data is passed top-down (parent to child) via props, but such usage can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.


Okay, enough with the introduction. Now we build a basic react app using create-react-app starter kit.
For those who would like to see the finished app, here is a preview of the complete code

First create a Todo.js file inside the src directory and add the following code to it. This component will render a button and a controlled input

import React, { useState } from "react";

export default function Todo() {
  const [todo, setTodo] = useState("");

  return (
    <form>
      <input
        type="text"
        name="todo"
        value={todo}
        onChange={(e) => setTodo(e.target.value)}
      />
      <button>Add todo </button>
    </form>
  );
}

Enter fullscreen mode Exit fullscreen mode

Next create a TodoList.js file and the src directory. This component will render our todo list, but for now it simply renders a h1 heading for the list.

import React from "react";

export default function TodoList() {
  return (
    <div>
      <h1> Todo List </h1>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Now we are getting to the interesting part.
To use Context in a react app, we need to create the context. So create a TodoContext.js in the same src directory.
Add the following line of code to it. Yes, I know we don't have a ./reducer.js file yet. Don't worry, we will create it soon.

import React, { createContext, useReducer } from "react";
import reducer, { initialState } from "./reducer";

Enter fullscreen mode Exit fullscreen mode

Create a context named todoContext by adding the next line of code

export let todoContext = createContext({});
Enter fullscreen mode Exit fullscreen mode

Next create function component Provider and pass down children as props to it.

export default function Provider({ children }) {

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

  const values = {
    state,
    dispatch
  };

  return (
    <>
      <todoContext.Provider value={values}>
         {children}
      </todoContext.Provider>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

let's explain the code above.

  1. useReducer - The useReducer Hook is similar to the useState Hook. It allows for custom state logic. If you find yourself keeping track of multiple pieces of state that rely on complex logic, useReducer may be useful. This hook accepts two arguments, a reducer and an initial state.
  2. Our Provider component returns a context.Provider component which accepts a value prop and passes it down to the children of our Provider component.

Here is the full code for the TodoContext.js

import React, { createContext, useReducer } from "react";
import reducer, { initialState } from "./reducer";

export let todoContext = createContext({});

export default function Provider({ children }) {

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

  const values = {
    state,
    dispatch
  };

  return (
    <>
      <todoContext.Provider value={values}>
        {children}
      </todoContext.Provider>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

Let's create our reducer.js file and add the code below to it

import { ADD_TODO } from "./constants";

export const initialState = {
  todos: []
};

const reducer = (state, action) => {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
    default:
      return state;
  }
};

export default reducer;

Enter fullscreen mode Exit fullscreen mode

The above code creates an initial state and initializes todos to an empty array. We then added a reducer function, a reducer function accepts a state and action as arguments and returns the updated state based on the type of action dispatched, which in our case we have an action called ADD_TODO that will be defined in constants.js.
Create the file constants.js and add following code to it

export const ADD_TODO = "ADD TODO";
Enter fullscreen mode Exit fullscreen mode

As we can see, constants.js simply creates and exports a string variable.

Next we update the code inside our app.js file by importing the TodoContext.js, Todo.js and TodoList.js component.
app.js now should look like this

import React, { Component } from "react";
import Todo from "./Todo";
import TodoList from "./TodoList";
import Provider from "./TodoContext";

export default class App extends Component {
  render() {
    return (
      <div className="App">
        <Provider>
          <Todo />
          <TodoList />
        </Provider>
      </div>
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Since we have wrapped our components in a Provider component, we can now be sure that we have access to the context in our Todo.js and TodoList.js.
We can update out Todo.js file to now access the context value and also create a function addTodoHandler that dispatches the ADD_TODO action type.
Below is the complete code for Todo.js

import React, { useState, useContext } from "react";
import { todoContext } from "./TodoContext";
import { ADD_TODO } from "./constants";

export default function Todo() {
  const [todo, setTodo] = useState("");
  const { dispatch } = useContext(todoContext);

  const addTodoHandler = (e) => {
    e.preventDefault();
    dispatch({ type: ADD_TODO, payload: todo });
    setTodo("");
  };

  return (
    <form>
      <input
        type="text"
        name="todo"
        value={todo}
        onChange={(e) => setTodo(e.target.value)}
      />
      <button onClick={addTodoHandler}>Add todo </button>
    </form>
  );
}

Enter fullscreen mode Exit fullscreen mode

Cool. Now all we need to do is display our Todo whenever a user clicks the Add todo button. And we do this by mapping over our todos array defined in the reducer.js and returning the jsx.
Below is the complete code for the TodoList.js

import React, { useContext } from "react";
import { todoContext } from "./TodoContext";

export default function TodoList() {
  const { state } = useContext(todoContext);
  return (
    <div>
      <h1> Todo List </h1>
      {state.todos.length > 0 &&
        state.todos.map((todo, id) => {
          return <h4 key={id}>{todo}</h4>;
        })}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

That's it. Now you have learnt what context means, when to use it and what a reducer is and most importantly, you've learned how to implement them in an application, albeit a small one :).

Disclaimer: To build a todo app like this one, using context and reducer is kind of overkill is unnecessary. But to better demonstrate and explain these concepts, I had to choose an example that won't be too complex to understand.

Thank you for reading, you can reach out to me for suggestions or corrections.

Top comments (0)