useReducer
by Tomas TrescakΒ· Advanced React

Root Folder
Not Attempted
NameProgress
Introduction
Not Read
🍰 Player
Not Read
Setup
Not Attempted
NameProgress
Software Installation
Not Read
Project Setup
Not Read
Running and Testing
Not Read
React and ReactDOM
Not Read
πŸ’‘ Assignment 1: Welcome Message
Not Attempted
Submissions
Not Read
React
Not Attempted
NameProgress
JSX and Components
Not Read
Props
Not Read
πŸ‘Ύ Exercise: Props
Not Attempted
CSS Styles
Not Read
useState and Hooks
Not Read
πŸ‘Ύ Exercise: useState
Not Attempted
Conditional Rendering
Not Read
Lists
Not Read
πŸ‘Ύ Exercise: Lists
Not Attempted
Forms and Events
Not Read
πŸ‘Ύ Exercise: Forms
Not Attempted
πŸ’‘ Assignment 2: Front End
Not Attempted
Pure Components - memo
Lifecycle - useEffect
Expensive? - useMemo
DOM Interactions - useRef
forwardRef
useImperativeHandle
πŸ‘Ύ useImperativeHandle
Not Attempted
Context
useCallback
useId
useReducer
Infrastructure
Not Attempted
NameProgress
Database
Not Read
NextAuth and Github Authentication
Not Read
Prisma and ORM
Not Read
Project Setup
Not Read
Project Authentication
Not Read
APIs
Not Attempted
NameProgress
APIs
Not Read
APIs - Slides
Not Attempted
Rest APIs
Not Read
Rest APIs - Express.js
Not Read
ReastAPIs - Next.js
Not Read
Securing APIs
Not Read
Securing APIs - NextAuth
Not Read
tRPC
Not Attempted
NameProgress
tRPC
Not Read
tRPC - Routers
Not Read
tRPC - Server Rendering
Not Read
tRPC - Client Rendering
Not Read
Persisting Data
Not Read
Assignment 3: APIs
Not Read
0 / 300 XP

Lecture Transcript
If you prefer text to video, check out the transcript of the presentation above

The useReducer hook is an alternative to useState for managing state in React functional components. It is particularly useful for complex state logic involving multiple sub-states or when the next state depends on the previous state.

With useState you have to define a state update function per each state variable. With useReducer, you use an independent reducer function to determine state changes. More on this later.

useState hold only a single piece of state, while useReducer defines the state, the dispatch and reducer function. A little more to worry about but simplifies things in complex states.

Last, with a complex state useState can become messy with multiple setState functions. Also, it is difficult to test the functionality of state change. With useReducer, all state changes are neatly organised into a single function.

Let’s refactor an example with a simple word counter delete, and reset actions evolve from useState to useReducer.

We use two state variables, word and count, holding the word length.

In the change handler, we update both state variables

In the delete handler we remove one letter from the end if possible and update count

The reset handler resets to an empty word.

The form provides the text input and buttons to handle delete and reset.

Already, we can see that adding many other handlers can lead to spaghetti code and confusion. Let's simplify this!

Let's convert the useState example to useReducer. We make sure everything is type safe and first we define the shape of our state, containing the word and count of its letters.

Then, we define all the actions we want our reducer to support. As you add more functionality, you add a new function name and shape of the passed parameters.

Then, we define the reducer, which accepts two parameters: the current state and the action being dispatched. The action contains two properties: the type or name of the action and the value, which is the payload of the action. Now, we onlyt have to implement a functionality for each action type, in our case ...

...changing the word and recalculating word length ...

... deleting the last letter ...

...resetting the word ...

... and also checking if we handled all the possible cases. Typescript is very helpful in this case, as dispatching a wrong action name shows compile time error.

The form now becomes much simpler. Firsr, we initialise the state and dispatch dispatch function using the useReducer hook.

Last, instead of calling setState, we dispatch the correct action identified by type with the required value.

Overall, useReducer prides several advantages over useState, but it's up to you if you decide to use it.

useReducer delivers cleaner state management for complex logic.

Centralized state transitions in a single reducer function.

Easier to debug and test state logic independently on your component

Scales much better when adding more actions or state properties.

Description
All the extra information about this section

The useReducer hook is an alternative to useState for managing state in React functional components. It is particularly useful for complex state logic involving multiple sub-states or when the next state depends on the previous state.


Key Differences Between useState and useReducer

FeatureuseStateuseReducer
ComplexityIdeal for simple state logicSuitable for complex state logic
State UpdatesDirectly set state via updaterUse a reducer function to determine state changes
StructureSingle piece of stateState + Dispatch + Reducer Function
ScalabilityCan become messy with multiple statesOrganizes updates into a single function

When to Use useReducer Over useState

  • When state logic is complex or interdependent.
  • When state updates involve multiple actions.
  • When you need a clean way to handle state transitions (like in a finite state machine).

Example: From useState to useReducer

Let’s refactor an example where a simple word counter with delete and reset actions evolves from useState to useReducer.


Step 1: Using useState

Here’s a counter example with useState:

import React, { render, useState } from "react";

const CounterWithState = () => {
  const [count, setCount] = useState(0);
  const [word, setWord] = useState("");

  function changeHandler(e) {
    setWord(e.currentTarget.value);
    setCount(e.currentTarget.value.length);
  }
  
  function del() {
    if (word.length) {
      setWord(word.slice(0, -1));
      setCount(count - 1);
    }
  }
  
  function reset() {
    setWord("");
    setCount(0)
  }

  return (
    <div>
      <h1>Counter with useState</h1>
      <p>Letters: {count}</p>
      <input value={word} onChange={changeHandler} />
      <button onClick={del}>Delete</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

render(<CounterWithState />)

Step 2: Refactor to useReducer

Here, we replace useState with useReducer for a cleaner and more scalable approach.


1. Define the Reducer Function

The reducer function determines how state changes based on an action.

type State = {
  word: string,
  count: number
}

type Action = 
  | { type: 'WORD', value: string }
  | { type: 'DELETE' }
  | { type: 'RESET' }
  
const reducer = (state: State, action: Action) => {
  switch (action.type) {
    case "WORD":
      return { count: action.value.length, word: action.value };
    case "DELETE":
      return state.word.length 
        ? { count: state.count - 1, word: state.word.splice(0, -1) }
        : state;
    case "RESET":
      return { word: '', count: 0 };
    default:
      throw new Error("Action not handled");
  }
};

2. Implement the Component

Here’s the updated counter using useReducer:

Copy code

import React, { useReducer } from "react";

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

  return (
    <div>
      <h1>Counter with useState</h1>
      <p>Letters: {state.count}</p>
      <input value={state.word} onChange={(e) => dispatch('WORD', e.currentTarget.value)} />
      <button onClick={() => dispatch('DELETE')}>Delete</button>
      <button onClick={() => dispatch('RESET')}>Reset</button>
    </div>
  );
};

export default CounterWithReducer;

How It Works:

  1. Reducer Function: Handles all state transitions based on the action's type.
  2. Initial State: { count: 0, word: β€œβ€ } is passed as the initial state to useReducer.
  3. Dispatch: Instead of calling setState, we call dispatch with an action object.

Advantages of useReducer Over useState:

  • Cleaner state management for complex logic.
  • Centralized state transitions in a single reducer function.
  • Easier to debug and test state logic.
  • Scalability when adding more actions or state properties.
  • Easier to test independently on React

Example Comparison: Before and After

FeatureuseState ExampleuseReducer Example
State StructureSingle state variableCentralized state object
Update LogicSpread across multiple setState callsEncapsulated in the reducer function
State UpdatesDirect calls to setStatedispatch triggers state updates

Conclusion

  • useState is great for simple scenarios with minimal state logic.
  • useReducer is a powerful tool for managing complex state logic and actions.

This refactoring demonstrates how useReducer provides better structure, cleaner code, and enhanced scalability for complex state management. Let me know if you'd like a more advanced example! πŸš€

Maggie

Discuss with Maggie
Use the power of generative AI to interact with course content

Discussion

0 comments
Loading editor ...
Remember to be polite and report any undesirable behaviour

Category

Empty

Labels

Discussion has no labels

1 participant

user avatar

Priority

Notifications

You're not receiving notifications from this thread.
Course Outline