Is there any way to use async await in SetState - javascript

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>
);
}

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)}

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.

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.
};

Related

Initialize state with async function data NextJS? [duplicate]

My component relies on local state (useState), but the initial value should come from an http response.
Can I pass an async function to set the initial state? How can I set the initial state from the response?
This is my code
const fcads = () => {
let good;
Axios.get(`/admin/getallads`).then((res) => {
good = res.data.map((item) => item._id);
});
return good;
};
const [allads, setAllads] = useState(() => fcads());
But when I try console.log(allads) I got result undefined.
If you use a function as an argument for useState it has to be synchronous.
The code your example shows is asynchronous - it uses a promise that sets the value only after the request is completed
You are trying to load data when a component is rendered for the first time - this is a very common use case and there are many libraries that handle it, like these popular choices: https://www.npmjs.com/package/react-async-hook and https://www.npmjs.com/package/#react-hook/async. They would not only set the data to display, but provide you a flag to use and show a loader or display an error if such has happened
This is basically how you would set initial state when you have to set it asynchronously
const [allads, setAllads] = useState([]);
const [loading, setLoading] = useState(false);
React.useEffect(() => {
// Show a loading animation/message while loading
setLoading(true);
// Invoke async request
Axios.get(`/admin/getallads`).then((res) => {
const ads = res.data.map((item) => item._id);
// Set some items after a successful response
setAllAds(ads):
})
.catch(e => alert(`Getting data failed: ${e.message}`))
.finally(() => setLoading(false))
// No variable dependencies means this would run only once after the first render
}, []);
Think of the initial value of useState as something raw that you can set immediately. You know you would be display handling a list (array) of items, then the initial value should be an empty array. useState only accept a function to cover a bit more expensive cases that would otherwise get evaluated on each render pass. Like reading from local/session storage
const [allads, setAllads] = useState(() => {
const asText = localStorage.getItem('myStoredList');
const ads = asText ? JSON.parse(asText) : [];
return ads;
});
You can use the custom hook to include a callback function for useState with use-state-with-callback npm package.
npm install use-state-with-callback
For your case:
import React from "react";
import Axios from "axios";
import useStateWithCallback from "use-state-with-callback";
export default function App() {
const [allads, setAllads] = useStateWithCallback([], (allads) => {
let good;
Axios.get("https://fakestoreapi.com/products").then((res) => {
good = res.data.map((item) => item.id);
console.log(good);
setAllads(good);
});
});
return (
<div className="App">
<h1> {allads} </h1>
</div>
);
}
Demo & Code: https://codesandbox.io/s/distracted-torvalds-s5c8c?file=/src/App.js

Issues upgrading async componentDidMount() to async useEffect()

// UPDATE: The issue was using the state immediately after setting it inside useEffect(). See my answer HERE for details.
I'm trying to upgrade one of my React app pages from class component to functional component with Hooks. However, I have some issues due to some async functions.
The way the old page behaves is that in componentDidMount() some data is async fetched from the database and displayed. It works properly, myName and myValue are displayed correctly.
// OLD APPROACH - CLASS COMPONENT
class MyPage extends Component {
constructor(props) {
super(props);
this.state = {
myName: null,
myValue: undefined,
}
}
componentDidMount = async () => {
try {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
this.setState({ myName, myValue });
} catch (error) {
alert(
"Some errors occured when fetching from DB"
);
console.error(error);
}
}
render() {
return (
<div>
<h1>{this.state.myName}</h1>
<h1>{this.state.myValue}</h1>
</div>
)
}
export default MyPage
I tried to update the page by carefully following this response.
// NEW APPROACH - FUNCTIONAL COMPONENT WITH HOOKS
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
async function fetchFromDatabase() {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
}
fetchFromDatabase();
}, [])
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
)
}
However, when I do this, they no longer get displayed. I supposed they remain "null" and "undefined". Apparently if I do a console.log(), they eventually get fetched, but only after the page is rendered without them, which is not what was happening in the first case.
Why exactly is this happening? Why is it getting displayed correctly in the first case but not in the second? As far as I know, useEffect() does the same thing as componentDidMount(). Should I proceed another way if I wish to call async functions inside useEffect()?
The useEffect hook and state updates are fine. Function components are instanceless though, so the this is just undefined. Fix the render to just reference the state values directly.
It's also good practice to handle errors when working with asynchronous code.
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
async function fetchFromDatabase() {
try {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
} catch(error) {
// handle any rejected Promises and thrown errors
}
}
fetchFromDatabase();
}, []);
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
);
}
First of all, you are giving the same name for your response as your useState(). Try using different names. Then, put just empty string into your useState() default value instead of null or undefined. Finally, you no longer need to use this but instead access directly the value. It should be something like this :
function MyPage() {
const [myName, setMyName] = useState('');
const [myValue, setMyValue] = useState('');
useEffect(() => {
async function fetchFromDatabase() {
const name = await getNameFromDatabase();
const value = await getValueFromDatabase();
setMyName(name);
setMyValue(value);
}
fetchFromDatabase();
}, [])
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
)
}
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
(async () => {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
})();
}, []);
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
);
}
Alright, so the code in the original post is correct, as other remarked. However, it is a very simplified/abstract version of the actual code I'm working on.
What I was doing wrong is that I was using the state in useEffect() immediately after setting it there.
Something like that:
// WRONG
let fetchedName= getNameFromDatabase();
setMyName(fetchedName);
if(myName==="something") {
setMyValue(1000);
}
The conclusion is: Never use the state immediately after setting it in useEffect() or componentWillMount(), use an intermediary variable.
Instead do:
// CORRECT
let fetchedName= getNameFromDatabase();
setMyName(fetchedName);
if(fetchedName==="something") {
setMyValue(1000);
}

Rendering async promises through function components

Sorry if the post is duplicated i just find examples for class components.
I have this code:
export const getUniPrice = async () => {
const pair = await Uniswap.Fetcher.fetchPairData(HOKK, Uniswap.WETH[ETH_CHAIN_ID]);
const route = new Uniswap.Route([pair], Uniswap.WETH[ETH_CHAIN_ID]);
const priceUni = route.midPrice.toFixed(9);
return priceUni
}
It does work, answer me the promise object:
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "1106278.001628948"
What i would like to know is, how can i properly work with this object in order to be able to render it through function components? I'm doing something like this which obviously will not work because react doesn't render objects.
const Price = () => {
const { state, dispatch } = useContext(AppContext);
return(<>
{state.dex === 'uni' ? getUniPrice() : state.dex === 'cake'
? getCakePrice() : getMDexPrice()
}
</>)
}
Could someone give me a hint? This function is running outside a function component so I can't just use useState
You're right. The result of getUnitPrice() is a Promise, not a value, so what React does is it prints out the stringified version of that Promise. If you need the fulfilled value, you need a state value that will re-render the page if updated. Something like this:
const [price, setPrice] = useState('');
useEffect(() => {
getUnitPrice().then((p) => setPrice(p));
}, []);
...
<div>Price: {price}</div>
If you're using a class component, you can initialize the state the same way like this:
state = {
price: '',
}
async componentDidMount() {
const p = await getUniPrice();
this.setState({ price: p });
}

React does not wait for server call to complete and throws error

I have a working rest service that I want to invoke in react.
The code does display the list of countries.
import {React, useEffect, useState } from 'react'
export const Noc = () => {
const [nocs, setNoc] = useState();
useEffect(
() => {
const fetchNoc = async () => {
const response = await fetch('http://localhost:8080/countrydefinitions');
const data = await response.json();
setNoc(data);
};
fetchNoc();
},[]
);
return <div>
<div>NOC LIST</div>
{nocs.map(noc => <div>{noc.region}</div>)}
</div>
}
But most of the times I get this error
TypeError: Cannot read property 'map' of undefined
sometimes it prints the list soemetimes it does'nt. Is there some sort of a delay or wait that I need to introduce?
How can I introduce a delay or make sure that setnoc has been called and nocs has a value before printing it.
React will rerender when the props change. Indeed at the beginning its nothing yet.
2 things to make it more solid can be done.
Add a default value, so that map is available on the array.
const [nocs, setNoc] = useState([]);
And/Or wait until noc is not undefined anymore, and validating that It has a map function, before trying to use map.
{nocs && typeof nocs.map === 'function' && nocs.map(noc => <div>{noc.region}</div>)}
No. there is no need to introduce any delay/wait. That is already handled by the async/await syntax.
You can either set an initial value in your useState or early return a custom message if nocs is undefined.
If you are fetching from your own api, you can return a response with an error if the fetch request should fail. And then at client side, you can handle that error by wrapping your fetch call inside a try-catch block
import {useEffect, useState } from 'react'
export const Noc = () => {
const [nocs, setNoc] = useState();
useEffect(
() => {
const fetchNoc = async () => {
const response = await fetch('http://localhost:8080/countrydefinitions');
const data = await response.json();
setNoc(data);
};
try{
fetch()
}catch(error){
//handle error here
console.log(error);
}
},[]
);
if(!nocs){
return (<p>No NOC found</p>)
}
return <div>
<div>NOC LIST</div>
{nocs.map(noc => <div>{noc.region}</div>)}
</div>
}
at first render, nocs has no data then useEffect runs and the second render, nocs will have data.
you need to check if nocs is undefined
{nocs && nocs.length > 0 && nocs.map(noc => <div>{noc.region}</div>)}

React: Why can't I pass data from one functional component to another functional component?

I am a bit lost as to why I can't pass fetched data between two functional components. I tried to use Hooks and it returned an error message suggesting to use array for object[Promise]. Any idea what I missed here? I'm pretty new to React. Any help would be appreciated!
// Component A
function App() {
const apiURL = "https://services9.arcgis.com/M4Su6cnNT6vqupbh/arcgis/rest/services/COVID19_Data/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json";
const apiData = fetch(apiURL)
.catch(err=>console.log(err))
.then(data=>console.log(data));
return (
<div>
<h1>Hello</h1>
<DataVisualize data={apiData}/>
</div>
);
}
// Component B
function DataVisualize(props){
return <div>{props.data}</div>;
}
Your apiData does not contain the actual data you want - it's only a Promise. Its catch is also in the wrong place (put the .catch after the .thens), and you need to call .json or .text on the Response (returned by the resolve value of fetch) in order to get a Promise that resolves to the actual data you need.
Set some state when the final data is retrieved instead:
// Component A
function App() {
const apiURL = "https://services9.arcgis.com/M4Su6cnNT6vqupbh/arcgis/rest/services/COVID19_Data/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json";
const [data, setData] = useState();
useEffect(
() => {
fetch(apiURL)
.then(res => res.json())
.then(setData)
.catch(console.error); // handle errors
},
[] // run this only once, on mount
);
return (
<div>
<h1>Hello</h1>
{ data && <DataVisualize data={data}/> }
</div>
);
}
// Component B
function DataVisualize(props){
return <div>{props.objectIdFieldName}</div>;
}
Also note that the result doesn't appear to have any .data property (though it does have a objectIdFieldName property, so I put that in as an example).

Categories