DEV Community

Cover image for React useReducer. Easier than we think.
Abdullah al Mubin
Abdullah al Mubin

Posted on • Updated on

React useReducer. Easier than we think.

useReducer:

useReducer is one of the most popular react hooks. useReducer hook returns the current stage and a dispatch method. It is similar to the useState hook. If we want to make complex logic then useReducer may be very useful.

If you want to download the full demo project then click here Demo git.

Syntax:

useReducer(<reducer>, <initialState>)
useReducer(<reducer>, <initialState>, <init>)

Here, reducer function contains custom state logic and the initialState can be a sample default state value.

init is a function and it is used whenever we want to create the initial state lazily.

Image description

reducer:

A reducer is a function that is able to process user instruction/action. A reducer processes the existing state with user action and returns the new state.

below we will try to make a simple application for better understanding.

Todo Application:

The below image shows the UI of our application.

Image description

We can see that it contains a submit button, if we save it then it will show on the list, we can complete and remove that todo item.

We will make our code as simple as we can.

let try to make action type

Here, we will make 3 types of action,

  • Add Todo
  • Remove Todo
  • Complete Todo

I have created a new folder inside the src directory and the folder name is action. inside that folder, I have created an index.js file and put the below code.

// src/action/index.js

export const ADD_TODO = "ADD_TODO";
export const REMOVE_TODO = "REMOVE_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";

Enter fullscreen mode Exit fullscreen mode

We have created three constants for user action types.

let try to make reducer

We already know that a reducer basically is a function that contain complex logic. We need to pass value and instruction/action then it will change previous state to new state. Just like Redux but we don't need to initialize our reducer here.

I have created a new folder and named it reducer, inside that folder I have created an index.js file and put the below code,

import { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from './../action'

const reducer = (state, action) => {
    switch (action.type) {
        case ADD_TODO:
            const newTodo = {
                id: action.id,
                text: action.text,
                completed: false
            };
            return [...state, newTodo];
        case REMOVE_TODO:
            return state.filter((todo) => todo.id !== action.id);
        case COMPLETE_TODO:
            const completeTodo = state.map((todo) => {
                if (todo.id === action.id) {
                    return {
                        ...todo,
                        completed: !todo.completed
                    };
                } else {
                    return todo;
                }
            });
            return completeTodo;
        default:
            return state;
    }
};
export default reducer;

Enter fullscreen mode Exit fullscreen mode

Inside the reducer function, we used a switch statement to check user action. If the user want to add new todo, then the action type will be ADD_TODO and based on switch statement, it will perform ADD_TODO logic in that block. Finally it will return new state.

case ADD_TODO:
const newTodo = {
id: action.id,
text: action.text,
completed: false
};
return [...state, newTodo];

To Remove a todo task, the action type will be REMOVE_TODO, and based on the switch _ statement, it will perform _REMOVE_TODO logic in that block. Finally, it will return to the new state.

case REMOVE_TODO:
return state.filter((todo) => todo.id !== action.id);

To complete a todo, the action will be COMPLETE_TODO and it will perform his logic and return new state.

case COMPLETE_TODO:
const completeTodo = state.map((todo) => {
if (todo.id === action.id) {
return {
...todo,
completed: !todo.completed
};
} else {
return todo;
}
});
return completeTodo;

it's a simple example so I haven't divided UI into separate containers. I put everything inside app.js file.

let put useReducer in app.js

import { useReducer, useState } from "react";
import "./index.css";
import reducer from "./reducer";
import { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from './action'
export default function App() {
  const [id, setId] = useState(0);
  const [text, setText] = useState("");
  const initialState = [
    {
      id: id,
      text: "First todo Item",
      completed: false
    }
  ];

  //We could also pass an empty array as the initial state
  //const initialState = []

  const [state, dispatch] = useReducer(reducer, initialState);
  const addTodoItem = (e) => {
    e.preventDefault();
    const newId = id + 1;
    setId(newId);
    dispatch({
      type: ADD_TODO,
      id: newId,
      text: text
    });
    setText("");
  };
  const removeTodo = (id) => {
    dispatch({ type: REMOVE_TODO, id });
  };
  const completeTodo = (id) => {
    dispatch({ type: COMPLETE_TODO, id });
  };
  return (
    <div className="App">
      <h2>Todo Example</h2>
      <form className="input" onSubmit={addTodoItem}>
        <input value={text} onChange={(e) => setText(e.target.value)} />
        <button disabled={text.length === 0} type="submit">+</button>
      </form>
      <div className="todos">
        {state.map((todo) => (
          <div key={todo.id} className="todoItem">
            <p className="">{todo.completed ? <del>{todo.text}</del> : todo.text}</p>
            <div className="actionType">
              <span onClick={() => removeTodo(todo.id)}>βœ•</span>
              <span onClick={() => completeTodo(todo.id)}>βœ“</span>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In the above code, we can see that useReducer takes the reducer function and initialState as a parameter then it returns state and dispatch.

When someone types some text and clicks on + sign, then inside the addTodoItem function dispatch function will be called and it contains 3 parameters those are type (ADD_TODO), id and text. dispatch will trigger reducer function and based on type reducer function will add a new todo item and return new state.

dispatch({
type: ADD_TODO,
id: newId,
text: text
});

When someone clicks on βœ• the sign, then another dispatch function will be called, this time type will be REMOVE_TODO, and based on type and id, the reducer function removes the specific item and will return new state.

dispatch({ type: REMOVE_TODO, id });

When someone clicks on βœ“ then dispatch function will trigger reducer with action type COMPLETE_TODO, then reducer will update that object based on id and return new state.

dispatch({ type: COMPLETE_TODO, id });

Let's put some CSS in index.css file,

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.App {
  width: 205px;
  margin: auto;
}

.todoItem {
  width: 100%;
  float: left;
  border-bottom: 1px solid;
}

.todoItem p {
  float: left;
}

.actionType {
  float: right;
  margin-top: 15px;
}
Enter fullscreen mode Exit fullscreen mode

If you want to download the full demo project then click here Demo git.

That's all for today, next, we will learn about react useContext hook and how to use with useReducer.

See ya.

Top comments (0)