DEV Community

Cover image for Using useReducer for Complex State Logic
Reme Le Hane
Reme Le Hane

Posted on • Originally published at open.substack.com

Using useReducer for Complex State Logic

Skill: Manage Complex State with useReducer

Instead of juggling multiple useState calls, you can use useReducer to handle state transitions in a more structured way, similar to how Redux works.

When to Use useReducer

  • When your state has multiple interdependent variables (e.g., a form or a complex UI).

  • When your state transitions are complex and involve multiple actions.

  • When you want a cleaner and more scalable state management solution.

Example: Managing a Complex Form

Let’s use useReducer to handle the state of a form with multiple fields and actions:

import React, { useReducer } from "react";

// Define initial state
const initialState = {
  username: "",
  email: "",
  password: "",
  isSubmitting: false,
  error: null,
};

// Define a reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case "SET_FIELD":
      return { ...state, [action.field]: action.value };
    case "SUBMIT_START":
      return { ...state, isSubmitting: true, error: null };
    case "SUBMIT_SUCCESS":
      return { ...initialState }; // Reset form on success
    case "SUBMIT_ERROR":
      return { ...state, isSubmitting: false, error: action.error };
    default:
      throw new Error(`Unknown action type: ${action.type}`);
  }
};

const ComplexForm = () => {
  // Use useReducer to manage form state
  const [state, dispatch] = useReducer(formReducer, initialState);

  const handleChange = (e) => {
    const { name, value } = e.target;
    dispatch({ type: "SET_FIELD", field: name, value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    dispatch({ type: "SUBMIT_START" });

    try {
      // Simulate form submission
      await new Promise((resolve) => setTimeout(resolve, 1000));
      console.log("Form submitted:", state);
      dispatch({ type: "SUBMIT_SUCCESS" });
    } catch (error) {
      dispatch({ type: "SUBMIT_ERROR", error: "Submission failed!" });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Username:</label>
        <input
          name="username"
          value={state.username}
          onChange={handleChange}
        />
      </div>

      <div>
        <label>Email:</label>
        <input name="email" value={state.email} onChange={handleChange} />
      </div>

      <div>
        <label>Password:</label>
        <input
          name="password"
          type="password"
          value={state.password}
          onChange={handleChange}
        />
      </div>

      {state.error && <p style={{ color: "red" }}>{state.error}</p>}

      <button type="submit" disabled={state.isSubmitting}>
        {state.isSubmitting ? "Submitting..." : "Submit"}
      </button>
    </form>
  );
};

export default ComplexForm;
Enter fullscreen mode Exit fullscreen mode

How It Works

  1. State Centralization:
  • All form fields, submission state, and error handling are managed in a single state object.
  1. Reducer Logic:
  • Actions (SET_FIELD, SUBMIT_START, etc.) describe what happens.

  • The reducer updates the state based on these actions.

  1. Form Submission:
  • On form submission, the state transitions smoothly through SUBMIT_START, SUBMIT_SUCCESS, or SUBMIT_ERROR.

Why Use This?

  • Scalability: Adding more fields or actions (like reset) is straightforward.

  • Readability: State transitions are centralized and predictable.

  • Performance: State updates are handled in bulk, reducing re-renders compared to multiple useState calls.

This skill is invaluable for managing complex state logic and making your React components more robust!

Top comments (0)