Im 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 is there any way I can do it properly
codesanbox: https://codesandbox.io/s/infallible-mendeleev-6641iw?file=/src/App.js:0-614
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [value, setValue] = useState([]);
const run = async (x) => {
setValue((oldValue) => {
const newValue = [...oldValue];
const res = fetch(`https://fakestoreapi.com/products/${x}`);
const result = res.json();
console.log(result.title);
return newValue;
});
};
return (
<div className="App">
<button onClick={run(2)}>get result</button>
{value.map((item, index) => {
return <h2 key={index}>{value[index]}</h2>;
})}
</div>
);
}
CodePudding user response:
We 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)}
CodePudding user response:
You can fetch the data before calling the setState
function. Because your fetch
call is not depending on anything in the state, but just on the x
var that you have avaliable outside of the setState.
So simply call it before calling setState
, and once you have it, you'll call the setState
.
const run = async (x) => {
const res = await fetch(`https://fakestoreapi.com/products/${x}`);
const result = await res.json();
setValue((oldValue) => {
const newValue = [...oldValue];
console.log(result.title);
return newValue;
});
};
And you have a mistake when you call your run()
function in the onClick
event, you should change it to this:
<button onClick={() => run(2)}>get result</button>
CodePudding user response:
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:
- It attempts to load the inner children but places a
try..catch
in said loading process. - If an error is caught, and the caught error is a promise then Suspense will instead render the component in its
fallback
property. - After rendering what's in
fallback
, React awaits the caught promise. - 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:
- Create a
readXXX
function that is a suspender function (a function that throws a promise). - Call this function at the beginning of your inner Suspense component's code.
- 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.