Skip to content

Instantly share code, notes, and snippets.

@kingluddite
Created February 6, 2024 13:04
Show Gist options
  • Save kingluddite/e1ca27ec5df0954055806ed4d6bda3a0 to your computer and use it in GitHub Desktop.
Save kingluddite/e1ca27ec5df0954055806ed4d6bda3a0 to your computer and use it in GitHub Desktop.
Using prevStage argument in React

Using prevStage argument in React

Is there a difference between

setDeveloperState(prevState => ({
  ...prevState,
  excitementLevel: newExcitementLevel,
  // You can update other properties here as well
}));

and this:

setDeveloperState({ ...developerState, mood: 'lazy' });

Yes, there's a significant difference between the two approaches.

In the first approach:

setDeveloperState(prevState => ({
  ...prevState,
  excitementLevel: newExcitementLevel,
  // You can update other properties here as well
}));

You're using the functional update form of setState, where the new state is computed based on the previous state. This is recommended when you need to update the state based on its previous value because it guarantees that you're always working with the latest state. This is particularly important when dealing with asynchronous updates or when there are multiple state updates happening in close succession.

In the second approach:

setDeveloperState({ ...developerState, mood: 'lazy' });

You're directly updating the state by creating a new object that includes the previous state's properties along with the updated properties. This approach is fine for simple state updates where you're not dependent on the previous state. However, it can lead to bugs if you rely on the previous state's value, especially in cases where state updates are asynchronous or when dealing with concurrent updates.

In general, it's recommended to use the functional form of setState (setState(prevState => ...)) when updating state based on its previous value to avoid issues related to stale state or race conditions.

Can you give an example where using the second way instead using the prevState causes a bug?

Let's consider a scenario where the second approach (directly updating the state without using prevState) could lead to a bug.

Suppose you have a counter component where clicking a button increments the count by 1. You want to ensure that each click increments the count by the current count value plus 1.

Here's the component using the direct state update approach:

import React, { useState } from 'react';

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

  const handleClick = () => {
    // Direct state update without using prevState
    setCount({ count: count + 1 }); // Bug: This will not update correctly
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default Counter;

In this implementation, when the handleClick function is called, it directly updates the count state by incrementing it without considering the previous state. This can lead to issues because state updates in React are asynchronous and may not reflect the most up-to-date state.

Now, let's see how the bug can manifest:

  1. User clicks the "Increment" button multiple times in quick succession.
  2. React batches state updates and processes them asynchronously.
  3. Because each state update depends on the previous state, the updated count is incorrect because it's based on the stale state.
  4. As a result, the count may skip increments or not reflect the correct value.

To fix this bug and ensure that each click increments the count correctly, you should use the functional update form of setState, which provides the previous state as an argument:

const handleClick = () => {
  setCount(prevCount => prevCount + 1); // Correct way using prevState
};

Using prevState ensures that you're always working with the latest state, avoiding issues related to stale state and ensuring that state updates are applied correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment