Home > Web Front-end > JS Tutorial > body text

useReducer and how it is different from useState

Susan Sarandon
Release: 2024-10-30 20:04:30
Original
682 people have browsed it

useReducer and how it is different from useState

Table of Contents

  1. Introduction
  2. When to Use useState
  3. When to Use useReducer
  4. Example 1: Counter App with useState
  5. Example 2: Counter App with useReducer
  6. Example 3: Form Input Handling with useReducer
  7. Example 4: Building a quiz app with useReducer
  8. Comparison Between useState and useReducer
  9. Conclusion

Introduction

React offers two key hooks for managing state: useState and useReducer. While both are designed to handle state in functional components, they are used in different scenarios. This article explores the differences between the two and highlights when you should use each, with examples for better understanding

When to Use useState

useState is a simple and effective hook for handling local state when:

  • You have simple state to manage (like booleans, numbers, or strings).
  • You want direct updates to state with minimal setup.
  • The state does not have complex transitions or dependencies on multiple variables.

Basic Syntax

const [state, setState] = useState(initialState);
Copy after login
Copy after login
  • state: The current state.
  • setState: A function to update the state.
  • initialState:The initial state

When to Use useReducer

useReducer is useful when:

  • You have complex state logic.
  • Multiple state updates depend on one another.

Basic Syntax

const [state, dispatch] = useReducer(reducer, initialState);

Copy after login
Copy after login
  • state: The current state.
  • dispatch: A function to send an action to the reducer to trigger a state update.
  • reducer: A reducer is a pure function that takes two arguments: the current state and an action. It returns the new state based on the action.

Basic Syntax

const reducer = (state, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 };
        case 'DECREMENT':
            return { count: state.count - 1 };
        default:
            return state;
    }
}
Copy after login
Copy after login
  • Action: An action is an object that describes what change should happen
    It typically has a type property and optionally a payload.
    The type tells the reducer what kind of state change to make.
    The payload carries any additional data needed for the change.

  • InitialState:The initial state ,just like initialstate in useState.

Example 1 counter app with useState

const [state, setState] = useState(initialState);
Copy after login
Copy after login

Explanation

  • We use useState to track the count value.
  • We have two buttons: one to increment and one to decrement the count state.
  • The state is updated directly using the setCount function.

Example 2: Counter App with useReducer

const [state, dispatch] = useReducer(reducer, initialState);

Copy after login
Copy after login

Explanation

  • The reducer function controls how the state should change based on the action dispatched.
  • Instead of directly setting the state, we dispatch actions (increment, decrement) to trigger changes.

Example 3: Form Input Handling with useReducer

Let’s expand the concept to handling a form with multiple input fields. This scenario is ideal for useReducer since it updates multiple state properties based on actions.

const reducer = (state, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 };
        case 'DECREMENT':
            return { count: state.count - 1 };
        default:
            return state;
    }
}
Copy after login
Copy after login

Explanation

  • The reducer manages the form state by updating different properties (name, email) based on the action’s type.
  • Dispatch sends the action to the reducer to update the state. The payload carries the data (e.g., the input value).

Example 4: Building a quiz app with useReducer

Note: styling was done with tailwindcss

import React, { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}


Copy after login

Explanation

*initial state with useReducer

import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

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

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

Copy after login
  • Reducer Function
import React, { useReducer } from 'react';

const initialState = {
  name: '',
  email: ''
};

function reducer(state, action) {
  switch (action.type) {
    case 'setName':
      return { ...state, name: action.payload };
    case 'setEmail':
      return { ...state, email: action.payload };
    default:
      return state;
  }
}

export default function Form() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <input
        type="text"
        value={state.name}
        onChange={(e) => dispatch({ type: 'setName', payload: e.target.value })}
        placeholder="Name"
      />
      <input
        type="email"
        value={state.email}
        onChange={(e) => dispatch({ type: 'setEmail', payload: e.target.value })}
        placeholder="Email"
      />
      <p>Name: {state.name}</p>
      <p>Email: {state.email}</p>
    </div>
  );
}




Copy after login

The reducer handles three actions:

  • SELECT_OPTION: When user selects an answer
  • NEXT_QUESTION: When moving to the next question
  • RESTART: When restarting the quiz

Styling Logic

import React, { useReducer } from 'react';

// Quiz data with detailed explanations
const quizData = [
  {
    question: "What hook is used to handle complex state logic in React?",
    options: ["useState", "useReducer", "useEffect", "useContext"],
    correct: 1,
    explanation: "useReducer is specifically designed for complex state management scenarios."
  },
  {
    question: "Which function updates the state in useReducer?",
    options: ["setState", "dispatch", "update", "setReducer"],
    correct: 1,
    explanation: "dispatch is the function provided by useReducer to trigger state updates."
  },
  {
    question: "What pattern is useReducer based on?",
    options: ["Observer Pattern", "Redux Pattern", "Factory Pattern", "Module Pattern"],
    correct: 1,
    explanation: "useReducer is inspired by Redux's state management pattern."
  }
];

// Initial state with feedback state added
const initialState = {
  currentQuestion: 0,
  score: 0,
  showScore: false,
  selectedOption: null,
  showFeedback: false, // New state for showing answer feedback
};

// Enhanced reducer with feedback handling
const reducer = (state, action) => {
  switch (action.type) {
    case 'SELECT_OPTION':
      return {
        ...state,
        selectedOption: action.payload,
        showFeedback: true, // Show feedback when option is selected
      };
    case 'NEXT_QUESTION':
      const isCorrect = action.payload === quizData[state.currentQuestion].correct;
      const nextQuestion = state.currentQuestion + 1;
      return {
        ...state,
        score: isCorrect ? state.score + 1 : state.score,
        currentQuestion: nextQuestion,
        showScore: nextQuestion === quizData.length,
        selectedOption: null,
        showFeedback: false, // Reset feedback for next question
      };
    case 'RESTART':
      return initialState;
    default:
      return state;
  }
};

const Quiz = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { currentQuestion, score, showScore, selectedOption, showFeedback } = state;

  const handleOptionClick = (optionIndex) => {
    dispatch({ type: 'SELECT_OPTION', payload: optionIndex });
  };

  const handleNext = () => {
    if (selectedOption !== null) {
      dispatch({ type: 'NEXT_QUESTION', payload: selectedOption });
    }
  };

  const handleRestart = () => {
    dispatch({ type: 'RESTART' });
  };

  if (showScore) {
    return (
      <div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-4">
        <div className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full">
          <h2 className="text-2xl font-bold text-center mb-4">Quiz Complete!</h2>
          <p className="text-xl text-center mb-6">
            Your score: {score} out of {quizData.length}
          </p>
          <button
            onClick={handleRestart}
            className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition-colors"
          >
            Restart Quiz
          </button>
        </div>
      </div>
    );
  }

  const currentQuizData = quizData[currentQuestion];
  const isCorrectAnswer = (optionIndex) => optionIndex === currentQuizData.correct;

  return (
    <div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-4">
      <div className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full">
        <div className="mb-6">
          <p className="text-sm text-gray-500 mb-2">
            Question {currentQuestion + 1}/{quizData.length}
          </p>
          <h2 className="text-xl font-semibold mb-4">{currentQuizData.question}</h2>
        </div>

        <div className="space-y-3 mb-6">
          {currentQuizData.options.map((option, index) => {
            let buttonStyle = 'bg-gray-50 hover:bg-gray-100';

            if (showFeedback && selectedOption === index) {
              buttonStyle = isCorrectAnswer(index) 
                ? 'bg-green-100 border-2 border-green-500 text-green-700'
                : 'bg-red-100 border-2 border-red-500 text-red-700';
            }

            return (
              <button
                key={index}
                onClick={() => handleOptionClick(index)}
                disabled={showFeedback}
                className={`w-full p-3 text-left rounded-lg transition-colors ${buttonStyle}`}
              >
                {option}
              </button>
            );
          })}
        </div>

        {showFeedback && (
          <div className={`p-4 rounded-lg mb-4 ${
            isCorrectAnswer(selectedOption)
              ? 'bg-green-50 text-green-800'
              : 'bg-red-50 text-red-800'
          }`}>
            {isCorrectAnswer(selectedOption)
              ? "Correct! "
              : `Incorrect. The correct answer was: ${currentQuizData.options[currentQuizData.correct]}. `}
            {currentQuizData.explanation}
          </div>
        )}

        <button
          onClick={handleNext}
          disabled={!showFeedback}
          className={`w-full py-2 px-4 rounded transition-colors ${
            !showFeedback
              ? 'bg-gray-300 cursor-not-allowed'
              : 'bg-blue-500 text-white hover:bg-blue-600'
          }`}
        >
          Next Question
        </button>
      </div>
    </div>
  );
};

export default Quiz;


Copy after login

This code determines the button styling:

  • Default: Gray background
  • Correct answer: Green background with green border
  • Wrong answer: Red background with red border

Feedback Display

// Initial state
const initialState = {
  currentQuestion: 0,
  score: 0,
  showScore: false,
  selectedOption: null,
  showFeedback: false, // New state for feedback
};
Copy after login

This shows feedback after an answer is selected:

*Displays whether the answer was correct or incorrect
*Shows the correct answer if wrong
*Includes an explanation

Hosted link of the quiz app

quiztechnicalwriting.vercel.app

Comparison Between useState and useReducer

Feature useState useReducer
Best for Simple state Complex state logic
State Management Direct, using setState Managed through a reducer function
Boilerplate Code Minimal Requires more setup
State Update Inline with setState Managed by dispatch and reducer

Conclusion

Both useState and useReducer are powerful hooks for managing state in functional components. useState is best suited for simple state, while useReducer shines when handling more complex scenarios where state updates are closely related. Choosing the right one depends on the complexity of the state you need to manage.

The above is the detailed content of useReducer and how it is different from useState. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template