useReducer Hook
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
Similarity between useState and useReducer
useState and useReducerBoth hooks have 3 things in common:
State variable
Function to change the state
Initial value for the state

useState and useReducerDifference between useState and useReducer
useState and useReducerWith useState we define each state in a seperate variable. With useReducer we define all the states in a single object.

useReducer Hook Workflow
useReducer Hook WorkflowuseReducer Hook Workflow:
We call
dispatch()to anytime we want to update the state.Everytime
dispatch()is called it calls thereducer()function.In the
reducer()function thestateparameter refers to the current state andactionparameter refers to the first argument we pass to thedispatch()function.dispatch()typically pass two properties -typeandpayloadto theactionobject.We pass
typeproperty to theactionobject to indicate which state needs to be updated andpayoladproperty to theactionobject to send some data.We pass a
payloadproperty to theThe
reducer()function returns a brand new state and our component re-renders.

useReducer Workflowreducer() Rules
reducer() RulesSome rules:
Don't use async/await, request operations within
reducer()Don't modify state directly.


useReducer Hook Example
useReducer Hook ExampleSimple app using useReducerhook.
import React, {useState, useReducer} from "react"
const ACTIONS = {
    INCREMENT : "increment",
    DECREMENT : "decrement"
}
// `reducer` function takes 2 parameters - `Current State` and `action`. It returns a `New State`.
function reducer(state, action){
    switch(action.type){
        case ACTIONS.INCREMENT:
            return {count: state.count + 1}
        case ACTIONS.DECREMENT:
            return {count: state.count - 1}
        default:
            return state
    }
}
function App() {
    
    // useReducer hook accepts 2 parameters - a `reducer function` that performs action on State and a `value`(as object) that represents `Initial State` 
    const [state, dispatch] = useReducer(reducer, { count:0 }) // dispatch calls `reducer` function
    
    function increment() {
        dispatch({type: ACTIONS.INCREMENT})
    }
    
    function decrement() {
        dispatch({type: ACTIONS.DECREMENT})
    }
    
    return (
        <div>
            <h1>{state.count}</h1>
            <button onClick={increment}>Increment</button>
            <button onClick={decrement}>Decrement</button>
        </div>
    )
}
export default App
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
ReactDOM.render(<App />, document.getElementById("root"))
Managing Complex State with useReducer Hook
useReducer Hook// Import the React library and the useReducer hook from the react package
import { React, useReducer } from "react";
// Import the App.css stylesheet
import "./App.css";
// Define action types as constants
const INCREMENT = "increment";
const DECREMENT = "decrement";
const INPUTVAL = "change-value";
const ADD_VAL_TO_COUNT = "add-value";
// Define a reducer function that accepts a state and an action, and returns a new state
const reducer = (state, action) => {
  switch (action.type) {
    case INCREMENT:
      // Return a new state object with the count incremented by 1
      return {
        ...state,
        count: state.count + 1,
      };
    case DECREMENT:
      // Return a new state object with the count decremented by 1
      return {
        ...state,
        count: state.count - 1,
      };
    case INPUTVAL:
      // Return a new state object with the inputValue property set to the payload of the action
      return {
        ...state,
        inputValue: action.payload,
      };
    case ADD_VAL_TO_COUNT:
      // Return a new state object with the count incremented by the value of the inputValue property, and the inputValue property reset to 0
      return {
        ...state,
        count: state.count + state.inputValue,
        inputValue: 0,
      };
    default:
      // If the action type doesn't match any of the cases above, return the current state
      return state;
  }
};
// Define a functional component called App
function App() {
  // Declare a variable called initialCount and set it to 50
  let initialCount = 50;
  // Use the useReducer hook to create a state object and a dispatch function based on the reducer function and an initial state object
  const [state, dispatch] = useReducer(reducer, {
    count: initialCount,
    inputValue: 0,
  });
  console.log(state);
  // Define a function called increment that dispatches an action with the type INCREMENT
  function increment() {
    dispatch({
      type: INCREMENT,
    });
  }
  // Define a function called decrement that dispatches an action with the type DECREMENT
  function decrement() {
    dispatch({
      type: DECREMENT,
    });
  }
  // Define a function called handleChange that updates the inputValue property of the state object based on the value of an input field
  function handleChange(e) {
    const inputValue = parseInt(e.target.value) || 0;
    dispatch({
      type: INPUTVAL,
      payload: inputValue,
    });
  }
  // Define a function called handleSubmit that dispatches an action with the type ADD_VAL_TO_COUNT
  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: ADD_VAL_TO_COUNT,
    });
  }
  // Render the following JSX elements
  return (
    <>
      <h1>{state.count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          value={state.inputValue || ""}
          onChange={handleChange}
        />
        <button>Add it!</button>
      </form>
    </>
  );
}
// Export the App component as the default export of the module
export default App;
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
ReactDOM.render(<App />, document.getElementById("root"))
Managing more Complex State with useReducer Hook
useReducer Hookimport React from "react"
import {ACTIONS} from "./App"
function Todo({todo, dispatch}){
    return(
        <div style={{ color: todo.complete ? "#AAA" : "#000" }}>
            <span>{todo.name}</span>
            <button onClick={()=> dispatch({ type: ACTIONS.TOGGLE_TODO, payload: {id:todo.id} })}>Toggle</button>
            <button onClick={()=> dispatch({ type: ACTIONS.DELETE_TODO, payload: {id:todo.id} })}>Delete</button>
        </div>
    )
}
export default Todoimport React, {useReducer, useState} from "react"
import Todo from "./Todo"
export const ACTIONS = {
    ADD_TODO: 'add-todo',
    TOGGLE_TODO: 'toggle-todo',
    DELETE_TODO: 'delete-todo',
}
// `reducer` function takes 2 parameters - `Current State` and `action`. It returns a `New State`.
function reducer(todos, action){
    switch(action.type){
        case ACTIONS.ADD_TODO:
            return [...todos, newTodo(action.payload.name)]
        
        case ACTIONS.TOGGLE_TODO:
            return todos.map(todo =>{
                if (todo.id === action.payload.id){
                    return {...todo, complete: !todo.complete} // if id does match return a new list of items with altered 'complete' state
                }
                return todo // do nothing if id doesn't match
            })
        
        case ACTIONS.DELETE_TODO:
            return todos.filter(todo => todo.id !== action.payload.id) // Only keep the items with unmatched id's
            
        default:
            return todos
    }
}
function newTodo(name){
    return { id: Date.now(), name: name, complete:false }
}
function App() {
    
    // useReducer hook accepts 2 parameters - a `reducer function`  and a `value`(as object)
    const [todos, dispatch] = useReducer(reducer, [])
    const [name, setName] = useState("")
    console.log(todos)
    
    function handleSubmit(e){
        e.preventDefault()
        dispatch({ type: ACTIONS.ADD_TODO, payload: {name:name} }) // passing `name` parameter to be used within `reduce` function
        setName("")
    }
    
    return (
        <div>
           <form onSubmit={handleSubmit}>
                <input type="text" value={name} onChange={ (e) => setName(e.target.value)}></input>
           </form>
           {
               todos.map(todo => {
                   // Provide `dispatch` function prop to be used in `Todo` Component
                   return <Todo key={todo.id} todo={todo} dispatch={dispatch} />
               })
           }
        </div>
    )
}
export default App
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
ReactDOM.render(<App />, document.getElementById("root"))
Using immer with reducer()
immer with reducer()We can directly mutate state with immer package.
// Import the produce function from the immer package
import { produce } from "immer";
// Import the React library and the useReducer hook from the react package
import { React, useReducer } from "react";
// Import the App.css stylesheet
import "./App.css";
// Define action types as constants
const INCREMENT = "increment";
const DECREMENT = "decrement";
const INPUTVAL = "change-value";
const ADD_VAL_TO_COUNT = "add-value";
// Define a reducer function that accepts a state and an action, and returns a new state
const reducer = (state, action) => {
  switch (action.type) {
    // If the action type is INCREMENT, update the count property of the state by adding 1
    case INCREMENT:
      state.count = state.count + 1;
      return;
    // If the action type is DECREMENT, update the count property of the state by subtracting 1
    case DECREMENT:
      state.count = state.count - 1;
      return;
    // If the action type is INPUTVAL, update the inputValue property of the state with the action payload
    case INPUTVAL:
      state.inputValue = action.payload;
      return;
    // If the action type is ADD_VAL_TO_COUNT, add the value of the inputValue property to the count property, and set the inputValue property to 0
    case ADD_VAL_TO_COUNT:
      state.count = state.count + state.inputValue;
      state.inputValue = 0;
      return;
    // If the action type is not recognized, return the state as is
    default:
      return;
  }
};
// Define a functional component called App
function App() {
  // Declare a variable called initialCount and set it to 50
  let initialCount = 50;
  // Use the useReducer hook to create a state object and a dispatch function based on the reducer function and an initial state object
  const [state, dispatch] = useReducer(produce(reducer), {
    count: initialCount,
    inputValue: 0,
  });
  // Log the state object to the console
  console.log(state);
  // Define a function called increment that dispatches an action with the type INCREMENT
  function increment() {
    dispatch({
      type: INCREMENT,
    });
  }
  // Define a function called decrement that dispatches an action with the type DECREMENT
  function decrement() {
    dispatch({
      type: DECREMENT,
    });
  }
  // Define a function called handleChange that updates the inputValue property of the state object based on the value of an input field
  function handleChange(e) {
    const inputValue = parseInt(e.target.value) || 0;
    dispatch({
      type: INPUTVAL,
      payload: inputValue,
    });
  }
  // Define a function called handleSubmit that dispatches an action with the type ADD_VAL_TO_COUNT
  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: ADD_VAL_TO_COUNT,
    });
  }
  // Render the following JSX elements
  return (
    <>
      <h1>{state.count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          value={state.inputValue || ""}
          onChange={handleChange}
        />
        <button>Add it!</button>
      </form>
    </>
  );
}
// Export the App component as the default export of the module
export default App;
Last updated
Was this helpful?