Nghĩa Phạm
Nghĩa Phạm

Reputation: 332

Is there any way to use async await in SetState

I'm trying to SetState but inside it, I have to get some data that needs async/await and I know it can't be done so I wonder if is there any way I can do it properly

codesanbox: https://codesandbox.io/s/infallible-mendeleev-6641iw?file=/src/App.js:0-614

Edit: It's hard for me to get data before setState because If I only want to get data If the newValue satisfies a condition so Get data force to inside setState

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

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

  const run = async () => {
    setValue((oldValue) => {
      const newValue = [...oldValue];
      // do something makes newValue changes
      if (newValue == true) { // if newValue satisfy a condition
      const res = fetch(`https://fakestoreapi.com/products/${newValue.length}`);
      const result = res.json();
      newValue.push(result.title);
      }
      return newValue;
    });
  };

  return (
    <div className="App">
      <button onClick={run}>get result</button>
      {value.map((item, index) => {
        return <h2 key={index}>{value[index]}</h2>;
      })}
    </div>
  );
}

Upvotes: 2

Views: 4537

Answers (3)

Gabriele Petrioli
Gabriele Petrioli

Reputation: 196236

Why don't you run first the async code, and when the data are available set the state ?

const run = async (x) => {
  const res = await fetch(`https://fakestoreapi.com/products/${x}`);
  const result = await res.json();
  setValue((oldValue) => {
    // you have access to the fetched data here
    const newValue = [...oldValue];
    console.log(result.title);
    return newValue;
  });
};

And ofcourse the click handler should be

onClick={() => run(2)}

Upvotes: 2

Tehila
Tehila

Reputation: 1102

You can get the old value from the value variable which is always storing the current state, and do the if check on it instead.

And if the if condition on value was true - then you can call fetch and after that call setState and update the state.

If the condition was not true, there is no need to update the state since it stayed the same. See the code below:

const [value, setValue] = useState([]);

const run = async () => {
      
  //some condition on the current value
  if (value) {
    const res = await fetch( `https://fakestoreapi.com/products/${newValue.length}`);
    const result = await res.json();

    // And here apply the changes on the state
    setValue((oldValue) => {
      const newValue = [...oldValue];
      newValue.push(result.title);
      return newValue;
    });
  }

  //Outside the if block - no need to change the state.
};

Upvotes: 1

Jos&#233; Ram&#237;rez
Jos&#233; Ram&#237;rez

Reputation: 2380

There's a React component called Suspense. I think it first appeared in v16, but in all honesty I have only used it with React v18, so unsure if it will work for you.

I'll refer you to a live demo I have: wj-config Live Demo

Here I use <Suspense> to wrap a component that requires data that is asynchronously obtained, just like when you use fetch().

Suspense works like this:

  1. It attempts to load the inner children but places a try..catch in said loading process.
  2. If an error is caught, and the caught error is a promise then Suspense will instead render the component in its fallback property.
  3. After rendering what's in fallback, React awaits the caught promise.
  4. Once the caught promise resolves, Suspense retries rendering the child components.

I hear there are frameworks that provides the necessary mechanisms to use Suspense in a simple and expedite manner, but in my live demo I did it all myself. Is not too bad I think.

The procedure to use this is:

  1. Create a readXXX function that is a suspender function (a function that throws a promise).
  2. Call this function at the beginning of your inner Suspense component's code.
  3. Program the rest of your inner component as if the readXXX function has worked and returned the needed data.

Here's the code I have in the live demo to create suspender functions:

function suspenderWrapper(promise) {
  let isPending = true;
  let outcome = null;
  const suspender = promise.then(
    (r) => {
      outcome = r;
      isPending = false;
    },
    (e) => {
      outcome = e;
      isPending = false;
    }
  );
  return () => {
    if (isPending) {
      throw suspender;
    }
    return outcome;
  };
}

Open up my live code's demo and look for App.js where all the magic is shown.

Upvotes: 1

Related Questions