11

How to get the changed state after an async action, using React functional hooks? I have found a redux solution for this issue, or a react class component solution, but I am wondering if there is a simple react functional solution.

Here is the scenario:

  • create a functional react component with. few states
  • create several button elements that each alter a different state.
  • using one of the button elements, trigger an async action.
  • If there was any other change in the state, prior to receiving results from the async function, abort all other continuing actions.

Attached is a code sandbox example https://codesandbox.io/s/magical-bird-41ty7?file=/src/App.js

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const [counter, setCounter] = useState(0);
  const [asyncCounter, setAsyncCounter] = useState(0);
  return (
    <div className="App">
      <div>
        <button
          onClick={async () => {
            //sets the asyncCounter state to the counter states after a 4 seconds timeout
            let tempCounter = counter;
            await new Promise(resolve => {
              setTimeout(() => {
                resolve();
              }, 4000);
            });
            if (tempCounter !== counter) {
              alert("counter was changed");
            } else {
              setAsyncCounter(counter);
            }
          }}
        >
          Async
        </button>
        <label>{asyncCounter}</label>
      </div>
      <div>
        <button
          onClick={() => {
            //increases the counter state
            setCounter(counter + 1);
          }}
        >
          Add 1
        </button>
        <label>{counter}</label>
      </div>
    </div>
  );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
FZs
  • 16,581
  • 13
  • 41
  • 50
Shai Kimchi
  • 746
  • 1
  • 4
  • 22

3 Answers3

13

You can use a ref to keep track of the counter value independently

 const [counter, setCounter] = useState(0);
 const counterRef = useRef(counter)

Whenever you update counter you update counterRef as well:

const newCounter = counter + 1
setCounter(newCounter);
counterRef.current = newCounter

And then check it:

if (counterRef.current !== counter) {
   alert("counter was changed");
} else {
   setAsyncCounter(counter);
}

Codesandox

thedude
  • 9,388
  • 1
  • 29
  • 30
  • Thank you all, No one looked at this question for over a month (except one user who answered wrong and then withdrew his answer). amazing what offering a bounty does. – Shai Kimchi Jul 07 '20 at 11:12
2

I've found another answer by asking the question on facebook-rect github. Apparently, since setting a state is a function, it's first argument is the current state.

so it is possible to have access to the previous value by using this snippet when setting the counter value:

 setCounter(prevValue => {
          alert(prevValue + " " + counter);
          return counter + 1;
        });

https://github.com/facebook/react/issues/19270 https://reactjs.org/docs/hooks-reference.html#functional-updates

Shai Kimchi
  • 746
  • 1
  • 4
  • 22
1

As @thedude mentioned, you will need to use the useRef hook – it was made exactly for your use case, as the docs say: "It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes."

I think you might just want to add a simple boolean:

  const counterChanged = useRef(false);

and then when you update the counter, you update this too.

            counterChanged.current = true;
            setCounter(counter + 1);

and inside your async function, you set it to false and then check if it's been changed.

counterChanged.current = false;
            await new Promise(resolve => {
              setTimeout(() => {
                resolve();
              }, 4000);
            });
            if (counterChanged.current) {
            // alert