Simplifying React Hooks: useReducer

Introduction When managing complex states in React, useState often falls short. This is where useReducer comes in, providing a more structured way to handle state transitions. In this article, we’ll explain useReducer, provide TypeScript-based examples to ensure type safety and clarity and discuss its benefits. What is useReducer? useReducer is a React Hook that provides an alternative to useState. It is useful for predictably handling state logic, especially in complex scenarios where multiple state updates depend on each other. Syntax: const [state, dispatch] = useReducer(reducer, initialState); state: The current state value. dispatch: A function used to trigger state changes. reducer: A function that takes the current state and an action, then returns a new state. initialState: The default state when the component mounts. Why Use useReducer Instead of useState? Better state management: Suitable for complex state logic with multiple transitions. Predictability: State transitions are centralized in a reducer function. Improved debugging: Actions make state changes more traceable. Implementing useReducer in TypeScript Let’s implement a simple counter with useReducer. Step 1: Define State and Actions type State = { count: number; }; type Action = { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "RESET" }; Step 2: Create the Reducer Function const reducer = (state: State, action: Action): State => { switch (action.type) { case "INCREMENT": return { count: state.count + 1 }; case "DECREMENT": return { count: state.count - 1 }; case "RESET": return { count: 0 }; default: return state; } }; Step 3: Use useReducer in a Component import { useReducer } from "react"; const Counter = () => { const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( Count: {state.count} dispatch({ type: "INCREMENT" })}>Increment dispatch({ type: "DECREMENT" })}>Decrement dispatch({ type: "RESET" })}>Reset ); }; export default Counter; Handling Complex State When dealing with more complex states, useReducer scales better than useState. For example, managing a form’s state: Define State and Actions type FormState = { username: string; email: string; }; type FormAction = | { type: "CHANGE_USERNAME"; payload: string } | { type: "CHANGE_EMAIL"; payload: string } | { type: "RESET" }; Create the Reducer Function const formReducer = (state: FormState, action: FormAction): FormState => { switch (action.type) { case "CHANGE_USERNAME": return { ...state, username: action.payload }; case "CHANGE_EMAIL": return { ...state, email: action.payload }; case "RESET": return { username: "", email: "" }; default: return state; } }; Use in a Component const Form = () => { const [state, dispatch] = useReducer(formReducer, { username: "", email: "" }); return ( dispatch({ type: "CHANGE_USERNAME", payload: e.target.value })} placeholder="Username" /> dispatch({ type: "CHANGE_EMAIL", payload: e.target.value })} placeholder="Email" /> dispatch({ type: "RESET" })}>Reset Username: {state.username} Email: {state.email} ); }; export default Form; When Should You Use useReducer? When state logic is complex and interdependent. When the next state depends on the previous one. When you need more predictable state transitions. When managing state in large-scale applications. Conclusion useReducer is a powerful alternative to useState, especially for managing complex state logic. Using it with TypeScript enhances type safety and maintainability. By structuring state updates through actions and a reducer function, useReducer makes React applications more scalable and predictable.

Mar 12, 2025 - 19:35
 0
Simplifying React Hooks: useReducer

Introduction

When managing complex states in React, useState often falls short. This is where useReducer comes in, providing a more structured way to handle state transitions.

In this article, we’ll explain useReducer, provide TypeScript-based examples to ensure type safety and clarity and discuss its benefits.

What is useReducer?

useReducer is a React Hook that provides an alternative to useState. It is useful for predictably handling state logic, especially in complex scenarios where multiple state updates depend on each other.

Syntax:

const [state, dispatch] = useReducer(reducer, initialState);
  • state: The current state value.

  • dispatch: A function used to trigger state changes.

  • reducer: A function that takes the current state and an action, then returns a new state.

  • initialState: The default state when the component mounts.

Why Use useReducer Instead of useState?

  • Better state management: Suitable for complex state logic with multiple transitions.

  • Predictability: State transitions are centralized in a reducer function.

  • Improved debugging: Actions make state changes more traceable.

Implementing useReducer in TypeScript

Let’s implement a simple counter with useReducer.

Step 1: Define State and Actions

type State = {
  count: number;
};

type Action = { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "RESET" };

Step 2: Create the Reducer Function

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1 };
    case "DECREMENT":
      return { count: state.count - 1 };
    case "RESET":
      return { count: 0 };
    default:
      return state;
  }
};

Step 3: Use useReducer in a Component

import { useReducer } from "react";

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <h2>Count: {state.count}h2>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Incrementbutton>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrementbutton>
      <button onClick={() => dispatch({ type: "RESET" })}>Resetbutton>
    div>
  );
};

export default Counter;

Handling Complex State

When dealing with more complex states, useReducer scales better than useState. For example, managing a form’s state:

Define State and Actions

type FormState = {
  username: string;
  email: string;
};

type FormAction =
  | { type: "CHANGE_USERNAME"; payload: string }
  | { type: "CHANGE_EMAIL"; payload: string }
  | { type: "RESET" };

Create the Reducer Function

const formReducer = (state: FormState, action: FormAction): FormState => {
  switch (action.type) {
    case "CHANGE_USERNAME":
      return { ...state, username: action.payload };
    case "CHANGE_EMAIL":
      return { ...state, email: action.payload };
    case "RESET":
      return { username: "", email: "" };
    default:
      return state;
  }
};

Use in a Component

const Form = () => {
  const [state, dispatch] = useReducer(formReducer, { username: "", email: "" });

  return (
    <div>
      <input
        type="text"
        value={state.username}
        onChange={(e) => dispatch({ type: "CHANGE_USERNAME", payload: e.target.value })}
        placeholder="Username"
      />
      <input
        type="email"
        value={state.email}
        onChange={(e) => dispatch({ type: "CHANGE_EMAIL", payload: e.target.value })}
        placeholder="Email"
      />
      <button onClick={() => dispatch({ type: "RESET" })}>Resetbutton>
      <p>Username: {state.username}p>
      <p>Email: {state.email}p>
    div>
  );
};

export default Form;

When Should You Use useReducer?

  • When state logic is complex and interdependent.

  • When the next state depends on the previous one.

  • When you need more predictable state transitions.

  • When managing state in large-scale applications.

Conclusion

useReducer is a powerful alternative to useState, especially for managing complex state logic.

Using it with TypeScript enhances type safety and maintainability. By structuring state updates through actions and a reducer function, useReducer makes React applications more scalable and predictable.