Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the changed state after an async action, using React functional hooks

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>
like image 906
Shai Kimchi Avatar asked Jun 17 '20 08:06

Shai Kimchi


People also ask

How to render a page refresh using async function in react?

You could possibly use a React Ref to store the state of the state variable. Then update the state variable with the react ref. This will render a page refresh, and then use the React Ref in the async function.

How to update state with setState in react hooks?

In class-based React components, we can pass a callback into the 2nd argument of setState to run code when a state is updated with setState . With React hooks, we no longer have the setState method. Instead, we use state updater functions created with the useState hook to update states.

How to run a function after updating state with hooks?

Let’s look at how to do that with hooks. The first and most commonly used method to run a function after updating state is the useEffect hook. useEffect runs its function only when the items in the dependency array change. Sounds pretty spot on for what we want huh?

Does react tracked support async actions?

Now, because React Tracked is a wrapper around React Hooks and Context, it doesn’t support async actions natively. This post shows some examples how to handle async actions. It’s written for React Tracked, but it can be used without React Tracked. The example we use is a simple data fetching from a server.


2 Answers

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

like image 196
thedude Avatar answered Sep 28 '22 17:09

thedude


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
like image 22
SquattingSlavInTracksuit Avatar answered Sep 28 '22 15:09

SquattingSlavInTracksuit