0

Here's my code https://codesandbox.io/s/heuristic-cannon-lvse23?file=/src/App.js

import { useEffect, useState } from "react";

import "./styles.css";

function useAsyncState() {
  const [status, setStatus] = useState("idle");

  function trigger(value) {
    setStatus(`sending ${value}`);
    setTimeout(function () {
      setStatus("done");
    }, 1500);
  }

  return [status, trigger];
}

export default function App() {
  const [value, setValue] = useState(0);
  const [status, trigger] = useAsyncState();

  useEffect(() => {
    if (status === "done") {
      setValue(value - 1);
    }
  }, [status, value]);

  return (
    <div className="App">
      <p>
        status: {status}
        <button onClick={() => trigger(value)}>send & decrement</button>
      </p>
      <p>
        value: {value}
        <button onClick={() => setValue(value + 1)}>increment</button>
      </p>
    </div>
  );
}

I have some kind of async action wrapped into a custom hook useAsyncState (simplified version of some real hook from my dependencies).

The problem is, I want to update value only once, when async action turns into done state.

However, changing state rerenders the component and since status is still done, value changes again, leading to endless loop of decrementing.

Also, action can be triggered again and I want to decrement value, when it's done again.

If I had an original Promise, I could do it in then callback, but I have only a hook like this.

Also, using useEffect delays value update to the next render, so it takes two renders to show decremeneted value. It's not a big deal, but it would be nice to do it in single render.

Is it ever possible to do it nice with such a custom hook? Or using raw Promises is the only way?

dmzkrsk
  • 2,011
  • 2
  • 20
  • 30
  • You can remove `value` from deps array and use functional update: `setValue((value) => value - 1);` in your useEffect hook. – Sergey Sosunov Feb 15 '23 at 21:28

1 Answers1

1

You can use a Functional Update instead:

If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.

useEffect(() => {
  if (status === "done") {
    setValue((prevValue) => prevValue - 1);
  }
}, [status]);
Corey Larson
  • 1,136
  • 9
  • 17