I have some problem using async functions in functional component in React.
I have a functional component that needs to render some number.
The function getNumber : for each id, performs an API call to retrive a number (associated with the specific id).
The function createArray creates an array of those numbers out of an array of ID's (idArray).
The problem is that the return of the function(component) access value createArray() [0] , and I get en error because the type is a promise (return don't wait for the async functions).
What is the best solution for this?
const getNumber = async (id: String) => {
const numberFromApi = await getNumberFromApi(id); //returns an object
return numberFromApi.num; //taking the num value out of the object
};
const createArray = async () => {
const arrayOfNumbers= await Promise.all(
idArray.map(id => getNumber (id))
);
return arrayOfNumbers;
}
return(
<div>{createArray()[0]} </div>)
All you can really do is to read the value from your component's state, and set the state when the promise resolves:
const [theValue, setTheValue] = useState(0); // or some other initial value, you choose what's best for your component
const getNumber = async (id: String) => {
const numberFromApi = await getNumberFromApi(id); //returns an object
return numberFromApi.num; //taking the num value out of the object
};
const createArray = async () => {
const arrayOfNumbers= await Promise.all(
idArray.map(id => getNumber (id))
);
return arrayOfNumbers;
}
useEffect(async () => {
const array = await createArray();
setTheValue(array[0]);
}, []);
return(
<div>{theValue} </div>
)
You may want to alter your component to show some sort of loading indicator while waiting for the Promise to resolve.
You will need to create some state that you can update when the promises return:
// your state
const [data, setData] = useState(false);
const getNumber = async (id: String) => {
const numberFromApi = await getNumberFromApi(id); //returns an object
return numberFromApi.num; //taking the num value out of the object
};
const createArray = () => {
Promise.all(
idArray.map(id => getNumber (id))
).then(
// use setData to set whatever you want displayed
);
}
// you render your data (or nothing if there is none)
return(<div>{data} </div>)
Related
To make my code cleaner I want to use fetched API data in a few different functions, instead of one big. Even though I 've did manage to reffer to that data in other functions, the problem is the API im a fetching throws different, randomized results every time it is called. And so the output from userData() does not equal that from userData2(), even though my intention is different and I'd like the result variable contents to be the same between functions.
const getData = () =>
fetch("https://opentdb.com/api.php?amount=10").then((response) =>
response.json()
);
const useData = async () => {
const result = await getData();
console.log(result);
};
const useData2 = async () => {
const result = await getData();
console.log(result);
};
Your getData() function returns a promise. One fun fact about promises is that while they can only resolve once, that resolved value can be accessed and used as many times as you want.
const dataPromise = getData();
const useData = async () => {
const result = await dataPromise;
console.log(result);
};
const useData2 = async () => {
const result = await dataPromise;
console.log(result);
};
Using await resolves the promise value, the equivalent of...
dataPromise.then((result) => {
console.log(result);
});
// or `dataPromise.then(console.log)` if you like brevity
I like to point this out about the fetch-api... you should always check the Response.ok property
const getData = async () => {
const res = await fetch("https://opentdb.com/api.php?amount=10");
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}
return res.json();
};
const user = useSelector((state) => state.user);
const [allPost, setAllPost] = useState([]);
const getAllPost = async () => {
const q = query(collection(db, "posts", "post", user.user.email));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.data());
const newPost = doc.data();
setAllPost([...allPost, newPost]);
});
};
useEffect(() => {
getAllPost();
}, []);
I have four documents in my firestore but setState only returning one although using forEach loop i can see all four object
The problem is that each time you use
setAllPost([...allPost, newPost]);
allPost is still the previous value because state value updates are asynchronous; allPost won't contain the new value until the next render.
You can either use the functional version of the state setter...
querySnapshot.forEach(doc => {
setAllPost(prev => prev.concat(doc.data()));
});
or simply build up a separate array and set them all at once
setAllPost(querySnapshot.docs().map(doc => doc.data()));
Personally, I'd prefer the latter.
I was wondering if anybody could help, I have a function that when it runs it will do a fetch and save the object array it gets to a variable, it will then loop through that object array and do another fetch and based on some data in that fetch it will add a new property/value to each object inside the array, I have that working fine, however I then want to filter the array by that new properties value and set the updated object array to state, the problem is the filter doesn't seem to be working, all I get back is the unfiltered data.
Code below, any help would be amazing.
import { useState, useEffect } from 'react'
import ResultsPage from './components/ResultsPage'
const App = () => {
const [results, setResults] = useState([])
const searchedRef = useRef()
useEffect(() => {
console.log(results)
}, [results])
const handleSearch = async () => {
let searched = searchedRef.current.value
let res = await fetch(`someAPI/${searched}`)
let data = await res.json()
searchResults = data
res = null
data = null
searchResults.map(async (element) => {
res = await fetch(`someAPI/${element}`)
data = await res.json()
element.name = data.name;
});
searchResults = searchResults.filter(element => element.name !== null);
setResults(searchResults)
}
return (
<>
<input type="text" ref={searchedRef} onKeyDown={handleSearch}/>
<ResultsPage results={results} />
</>
)
}
export default App
The map will not wait for each fetch to be done.
Async functions evaluate to Promises, so the mapping will evaluate to an array of Promises.
You need to use Promise.all to wait on an array of Promises.
await Promise.all(searchResults.map(/* ... */))
Example here.
I am getting data in an asynchronous way and consequently showing it inside the render() function using {data}.
Now my question is, how do I let the render() function wait until the variable is defined? Currently either the placeholder variable doesn't change or it never arrives.
This is my code,
let [data] = useState();
let storeData = async (value) => {
try {
await AsyncStorage.setItem('locatie', value)
} catch (e) {
console.log(e)
}
}
let getInfo = async () => {
data = await AsyncStorage.getItem('locatie');
return data;
}
useEffect(() => {
getInfo().then(r => console.log('getInfo called, data var = ' + data))
})
console.log('return() initiate, data var = ' + data)
And showing it here:
<TextInput value={data !== undefined ? data : 'placeholder'} onChangeText={(value) => storeData(value)}/>
Evidently, since it's asynchronous, render() happens before the function.
Any help would be appreciated. Please no flame I use React Native first time :)
Also, please note that it's inside a function, I am not using classes and will not use them.
You should learn more about using react hooks. Anyway, you can refer to below for your issue.
const [data, setData] = useState('');
const storeData = async (value) => {
try {
setData(value);
await AsyncStorage.setItem('locatie', value);
} catch (e) {
console.log(e);
}
};
const getInfo = async () => {
const res = await AsyncStorage.getItem('locatie');
setData(res);
};
useEffect(() => {
getInfo();
}, []);
Instead of let [data] = useState();
write const [data, setData] = useState();
and update the state inside the useEffect hook inside a then()
in your case, after the promise returns some data.
In my code, I have loadData() and useFilter(). I tried to run loadData() first as productsData need to be set for useFilter() to process productsData. However, I tried .then() but it still gives me undefined when I console log useFilter() How do I fix this?
const [productsData, setProductsData] = useState(null);
const loadData = async () => {
const { data } = await getAxios().get('/market/list').then(response => response.data)
setProductsData(data)
}
const useFilter = () => {
return productsData.map(data => (...))
//filters productsData
};
useEffect(()=>{
loadData().then(()=>useFilter());
},[])
Changing state asynchronous. So using state immediately after changing that may not be updated one.
You can use useEffect to call your function after state changed.
You can do this:
const [productsData, setProductsData] = useState(null);
const useFilter = () => {
return productsData.map(data => (...))
//filters productsData
};
useEffect(()=>{
const loadData = async () => {
const { data } = await getAxios().get('/market/list').then(response => response.data)
setProductsData(data)
}
loadData()
},[])
useEffect(()=>{
useFilter();
},[productsData])
Hello just by making a function async doesn't make it return a promise in order to use .then on function calls you must make sure they return promises
So one implementation you could do is :
const [productsData, setProductsData] = useState(null);
const loadData = () => {
return getAxios().get('/market/list').then(response => response.data)
}
const useFilter = () => {
return productsData.map(data => (...))
//filters productsData
};
useEffect(()=>{
loadData().then((data)=>{
setProductsData(data);
useFilter();
}
);
},[])
In the above implementation I return the promise that axios returns you can also return your own promise by creating a promise using new Promise() constructor
Do let me know if you face any issues