codewithjohn.dev
Published on

How to Use React useReducer() Hook

In this article we are going to explore useReducer Hook in react which is one of the most usefull hook that is used to manage complex state logic in react.

What is useReducer?

State updates are crucial part for implementing any feature in React. They allow you manage and modify the data that drives your application's functionality. If you are alredy familiar with react you know that useState is one of the most common ways to manage state just like the useState hook in react useReducer is also used to manage state it is an alternative to useState that is best suited for managing state in more complex scenarios.

Syntax:

const [state, dispatch] = useReducer(reducer, initialState);
  • state variable holds the current state value

  • dispatch is a function that allows us to send actions to the reducer.

  • reducer function is responsible for updating the state based on the dispatched action

  • initialState represents the initial value of the state.

Let's start with a simple counter example to understand the basic usage of useReducer:

Let's start define the initial state for the counter with single property count initialized with a value of 0.

const initialState = { count: 0 }

Next we will create our reducer as we mentioned above The reducer takes two parameters: state and action and it is responsible for updating the state based on the action type.

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    default:
      throw new Error()
  }
}

In the above reducer we have two action types

  • increment responsible for incrementing the count by 1 and It return a new state object
  • decrement responsible for decrementing the count by 1 and It return a new state object

We also added a default case for handling any unknown action type. The default case throws an error to indicate an invalid action type.

Let's now create the Counter component and use our reducer

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  )
}

In the above program we are using useReducer hook to manage the state. we passed the initial state and the reducer as arguments and and we distructor the current state and a dispatch function.

Now, let's explore a more complex example of a todo list using useReducer:

import React, { useReducer, useState } from 'react'

const initialState = {
  todos: [],
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'add':
      return { todos: [...state.todos, action.payload] }
    case 'toggle':
      return {
        todos: state.todos.map((todo) =>
          todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
        ),
      }
    case 'delete':
      return {
        todos: state.todos.filter((todo) => todo.id !== action.payload),
      }
    default:
      throw new Error()
  }
}

const TodoForm = ({ onAddTodo }) => {
  const [text, setText] = useState('')

  const handleChange = (e) => {
    setText(e.target.value)
  }

  const handleSubmit = (e) => {
    e.preventDefault()
    if (!text.trim()) return
    onAddTodo({
      id: Math.random(),
      text: text,
      completed: false,
    })
    setText('')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={text} onChange={handleChange} />
      <button type="submit">Add Todo</button>
    </form>
  )
}

const Todo = ({ todo, onToggleTodo, onDeleteTodo }) => {
  const handleToggle = () => {
    onToggleTodo(todo.id)
  }

  const handleDelete = () => {
    onDeleteTodo(todo.id)
  }

  return (
    <div>
      <input type="checkbox" checked={todo.completed} onChange={handleToggle} />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.text}</span>
      <button onClick={handleDelete}>Delete</button>
    </div>
  )
}

const TodoList = () => {
  const [state, dispatch] = useReducer(reducer, initialState)

  const handleAddTodo = (todo) => {
    dispatch({ type: 'add', payload: todo })
  }

  const handleToggleTodo = (id) => {
    dispatch({ type: 'toggle', payload: id })
  }

  const handleDeleteTodo = (id) => {
    dispatch({ type: 'delete', payload: id })
  }

  return (
    <div>
      <TodoForm onAddTodo={handleAddTodo} />
      {state.todos.map((todo) => (
        <Todo
          key={todo.id}
          todo={todo}
          onToggleTodo={handleToggleTodo}
          onDeleteTodo={handleDeleteTodo}
        />
      ))}
    </div>
  )
}

Conclusion

React's useReducer hook is a powerful tool for managing state in functional components. It provides a way to handle complex state logic by utilizing a reducer function. In this article, we explored the basics of useReducer with the help of an example to illustrate its usage and benefits. After reading this article, I hope you feel confident about using useReducer hook in you react project. Happy coding!

Like what you are reading?