useReducer and how it is different from useState

Table of Contents

Introduction
When to Use useState
When to Use useReducer
Example 1: Counter App with useState
Example 2: Counter App with useReducer
Example 3: Form Input Handling with useReducer
Example 4: Building a quiz app with useRed…


This content originally appeared on DEV Community and was authored by Adaobi Okwuosa

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);
  • 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);

  • 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;
    }
}
  • 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

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>
  );
}


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

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>
  );
}

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.

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>
  );
}




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, { 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;


Explanation

*initial state with useReducer

// Initial state
const initialState = {
  currentQuestion: 0,
  score: 0,
  showScore: false,
  selectedOption: null,
  showFeedback: false, // New state for feedback
};
  • Reducer Function
const reducer = (state, action) => {
  switch (action.type) {
    case 'SELECT_OPTION':
      return {
        ...state,
        selectedOption: action.payload,
        showFeedback: true, // Show feedback immediately
      };
    case 'NEXT_QUESTION':
      const isCorrect = action.payload === quizData[state.currentQuestion].correct;
      // ... rest of the logic

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

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';
}

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

{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>
)}

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

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.


This content originally appeared on DEV Community and was authored by Adaobi Okwuosa


Print Share Comment Cite Upload Translate Updates
APA

Adaobi Okwuosa | Sciencx (2024-10-29T01:19:53+00:00) useReducer and how it is different from useState. Retrieved from https://www.scien.cx/2024/10/29/usereducer-and-how-it-is-different-from-usestate/

MLA
" » useReducer and how it is different from useState." Adaobi Okwuosa | Sciencx - Tuesday October 29, 2024, https://www.scien.cx/2024/10/29/usereducer-and-how-it-is-different-from-usestate/
HARVARD
Adaobi Okwuosa | Sciencx Tuesday October 29, 2024 » useReducer and how it is different from useState., viewed ,<https://www.scien.cx/2024/10/29/usereducer-and-how-it-is-different-from-usestate/>
VANCOUVER
Adaobi Okwuosa | Sciencx - » useReducer and how it is different from useState. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/10/29/usereducer-and-how-it-is-different-from-usestate/
CHICAGO
" » useReducer and how it is different from useState." Adaobi Okwuosa | Sciencx - Accessed . https://www.scien.cx/2024/10/29/usereducer-and-how-it-is-different-from-usestate/
IEEE
" » useReducer and how it is different from useState." Adaobi Okwuosa | Sciencx [Online]. Available: https://www.scien.cx/2024/10/29/usereducer-and-how-it-is-different-from-usestate/. [Accessed: ]
rf:citation
» useReducer and how it is different from useState | Adaobi Okwuosa | Sciencx | https://www.scien.cx/2024/10/29/usereducer-and-how-it-is-different-from-usestate/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.