1

I am trying to manage multiple loading states inside a state dynamically with react hooks.

But, when I update the state object depending the result of an api call (success or error), it produces a very odd result.

As in my code example below, a button click will trigger the loading state for the button to be true.

When I click button 1 and then button 2 while button 1 is making a call (here I just simulated with timeout), they both set their loading states to true in order as expected.

Then, after some time, both loading states are set to false.

But, then both call are finished, button 1 loading state sets to true again...!?

I am not sure how I can solve this issue, and your help is appreciated. Thank you.

https://codesandbox.io/s/usestate-async-timeout-issue-oknvu

import React, { useState } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [loading, setLoading] = useState({});
  const handleOnClick = id => {
    setLoading({ ...loading, [id]: true });
    setTimeout(() => {
      // simulate the case for when error is returned after an api call.
      setLoading({ ...loading, [id]: false });
    }, 2000);
  };

  return (
    <div className="App">
      <h1>React UseState: Updating a state object</h1>
      {["1", "2"].map(id => {
        return !loading[id] ? (
          <button key={id} onClick={() => handleOnClick(id)}>
            Item {id}
          </button>
        ) : (
          "Loading..."
        );
      })}
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Noby Fujioka
  • 1,744
  • 1
  • 12
  • 15

2 Answers2

9

State updates are not synchronous in React, this is a common problem when you're updating state based on the previous state. I think you need to do the following change:

  const handleOnClick = id => {
    setLoading(prev => ({ ...prev, [id]: true }));
    setTimeout(() => {
      // simulate the case for when error is returned after an api call.
      setLoading(prev => ({ ...prev, [id]: false }));
    }, 2000);
  };

I've changed both the setLoading lines to use the previous state in their updates.

You can read more about this here:

https://reactjs.org/docs/hooks-reference.html#functional-updates

When to use React setState callback

simon
  • 1,095
  • 6
  • 11
0

I think this explains the problem you are facing: https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function

forgng
  • 108
  • 1
  • 1
  • 5