How to wait async data to start sync function - javascript

I get some data from an api call and set them in a state. Then I use this state variable in another function to filter some data. When the user opens the interface for the first time the data doesnt show because the sync function gets the empty data from the state.
Here is the code :
const [evQuestion, setEvQuestion] = useState();
const [answers, setAnswers] = useState();
const getEvaluationsQuestionsByOrganizations = async (evalId) => {
const response = await apiStandarts.get(`/evaluation-questions?organization_evaluation=${evalId}`);
setEvQuestion(response.data);
};
const evAnswers = () => {
const evAnswers = questions.map(q => {
return evQuestion?.map(ev => {
return q.question_options.find(i => i.id === ev.questOptionId)
});
});
const filterAnswers = evAnswers.map(q => {
return q?.filter(Boolean)
})
const answersToObject = filterAnswers.map(item => {
return convertArrayToObject(item)
});
const arr = {...answersToObject}
const obj2 = Object.fromEntries(
Object.entries(arr).map(([key, value]) => [key, value])
)
const obj3= Object.values(obj2).map(item => {
return {[item.question]: {...item}}
})
const savedAnswers = convertArrayToObject(obj3);
console.log(savedAnswers)
setAnswers(savedAnswers)
}
useEffect(() => {
getEvaluationsQuestionsByOrganizations();
evAnswers();
}, [])
I've tried to wrap the evAnswers function in a settimeout function but with no luck. How can I achieve this, any ideas?

Try adding another useEffect hook that depends on evQuestion state.
useEffect(() => {
getEvaluationsQuestionsByOrganizations();
}, []);
useEffect(() => {
evAnswers();
}, [evQuestion]);

the function getEvaluationsQuestionsByOrganizations(..) is defined as async function, but you are using it synchronously, in that case you should call your codes as below:
useEffect(() => {
const fetchedDataAPI = async () => {
return await getEvaluationsQuestionsByOrganizations();
};
fetchedDataAPI
.then(res => { evAnswers();})
.catch(err => {..});
;
}, []);

Related

Access variable outside useEffect in React.js

I am trying to fetch data as soon as page loads using useEffect(), but the problem is I don't know where to declare stateful const [orders, setOrders] = useState([]); when I put it above main function ( trying to make it global ) I get error saying React useState cannot be called top level. When I move it inside the function I cannot access it from fetchdata() function. I have to use setOrders from inside fetchdata()
What is the proper way to do it ?
function ActiveOrders () {
const [orders, setOrders] = useState([]);
useEffect(()=>{
fetchPost();
}, [])
}
const fetchPost = async () => {
await getDocs(collection(db, "orders"))
.then((querySnapshot)=>{
const newData = querySnapshot.docs
.map((doc) => ({...doc.data(), id:doc.id }));
setOrders(newData);
console.log(orders, newData);
})
}
Error I am getting : 'setOrders' is not defined
'orders' is not defined
The function fetchPost() should be in your component scope:
function ActiveOrders() {
const [orders, setOrders] = useState([]);
useEffect(() => {
fetchPost();
}, []);
const fetchPost = async () => {
await getDocs(collection(db, "orders")).then((querySnapshot) => {
const newData = querySnapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setOrders(newData);
console.log(orders, newData);
});
};
// Then, return()...
}
Move } after useEffect to end of the function for this problem. Next problem is that you are using "await" within "then" try write this code:
const {docs} = await getDocs(collection(db, "orders"))
const newData = docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setOrders(newData);
console.log(orders, newData)

Why useState variable gets undefined inside try catch statement in an asynchrone function?

I create a hook that manages the state of a single object with fetch to api. This hook exposes function to interact with this object.
// the hook
const useMyHook = () => {
const [myObject, setMyObject] = useState(null);
useEffect(() => {
const fetchData = async () => {
const data = await fetchSomething();
setMyObject(data);
}
fetchData();
}, []);
const updateMyObject = async () => {
console.log(myObject); // log : { ... }
try {
console.log(myObject); // log : undefined
// ...
} catch(err) {
// ...
}
};
return {
updateMyObject,
myObject
};
};
Then i use this hook inside a component and trigger updateMyObject() with a button.
// the component
const MyComponent = () => {
const { myObject, updateMyObject } = useMyHook();
return (
<button onClick={updateMyObject}>
Click me
</button>
);
};
How is this possible that before the try catch block the log is clean and inside the block i get undefined ?
I think this gonna work
useEffect(() => {
const fetchData = async () => {
const data = await fetchSomething();
setMyObject(data);
}
If(!myObject)
fetchData();
}, [myObject]);
Your code is perfectly alright !! There could be a problem in the fetchSomething() method. Ideally, it should return data, but it's not doing the same job.
Here is a small example. You can give it a try.
const fetchSomething = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts/1"
).then((res) => res.json());
return response;
};

React Native memory leak error after updating to Firebase version 9 onValue

I'm going through to update my code for the Firebase version 9 modular form. I am now using onValue. From what I'm reading it returns a function that removes the listener. But I'm still not doing it right because although it functions well at first, when I change the database on the backend with the app open I get the "can't perform a react state update on an unmounted component" error when I'm in a different app screen. See old and new code below please.
OLD CODE:
useEffect(() => {
loadListings();
},[]);
const loadListings = async () => {
setLoading(true);
updateInput('');
let testData = [];
let searchData = [];
db.ref('deals').once('value', (snapshot) =>{
snapshot.forEach((child)=>{
testData.push({
id: child.key,
title: child.val().hasOwnProperty('title') ? child.val().title : 'NA',
})
searchData.push(
child.val().title
)
})
})
.then(()=>{
checkMessages(testData);
setLoading(false);
})
.catch((error) => Sentry.Native.captureException('Error MessagesScreen function loadListings 1 ' + error));
}
NEW CODE:
useEffect(() => {
loadListings();
},[]);
const loadListings = async () => {
setLoading(true);
updateInput('');
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
let storeData = filterStores;
snapshot.forEach((childSnapshot)=>{
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
})
})
checkMessages(testData);
setLoading(false);
})
}
After receiving answer below I changed the useEffect to this instead and now it works:
useFocusEffect(
React.useCallback( () => {
async function fetchData() {
// You can await here
const response = await loadListings();
// ...
return () => response();
}
fetchData();
}, [])
);
You mentioned the unsubscribe function returned from onValue. In order to call it, I think you'll want to grab it from the invocation and then call it on some navigation state change.
Assuming you're using React Navigation, it might look something like this (using the useFocusEffect
import { useFocusEffect } from '#react-navigation/native';
function YourComponent() {
const [loading, setLoading] = React.useState(false)
useFocusEffect(
React.useCallback(async () => {
const unsubscribe = await loadListings();
return () => unsubscribe();
}, [])
);
const loadListings = async () => {
setLoading(true);
updateInput('');
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
let storeData = filterStores;
snapshot.forEach((childSnapshot)=>{
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
})
})
checkMessages(testData);
setLoading(false);
})
}
return <View />;
}
Also don't forget to either use async/await for your asynchronous loadListings function, or use .then(). Otherwise you'll be working with an unresolved promise.
I also found a related StackOverflow question that helped me get to this answer. Maybe that'll be of some use to you.

setState in multiple fetch requests

I'am doing the StarWars API task for the job interview.
my code look like that and I don't know in what place the setCharacters hook should be.
First the page is rendered and then the state is set.
I need the page to be rendered when all the fetches are done.
To try to be more efficient i changed the previous fetches into Promise.all() but right now I'am stuck with the setCharacters placement.
The previous topic can be seen here useEffect efficiency in Star Wars API
const api = `https://swapi.dev/api/people/`;
const [characters, setCharacters] = useState([]);
const [fetched, setFetched] = useState(false);
useEffect(() => {
const fetchOtherData = (characters) => {
const charactersWithAllData = [];
characters.forEach((character) => {
const homeworld = character.homeworld;
const species = character.species;
const vehicles = character.vehicles;
const starships = character.starships;
let urls = [homeworld, ...species, ...vehicles, ...starships];
Promise.all(
urls.map((url) => {
if (url.length) {
fetch(url)
.then((response) => response.json())
.then((data) => {
if (url.search("species") > 0) {
character.species = data.name;
}
if (url.search("planets") > 0) {
character.homeworld = data.name;
}
if (url.search("vehicles") > 0) {
character.vehicles.shift();
character.vehicles.push(data.name);
}
if (url.search("starships") > 0) {
character.starships.shift();
character.starships.push(data.name);
}
})
.catch((err) => console.error(err));
}
if (!url.length) {
if (url.search("species")) {
character.species = "Unspecified";
}
if (url.search("vehicles")) {
character.vehicles = "";
}
if (url.search("starships")) {
character.starships = "";
}
}
})
).then(charactersWithAllData.push(character));
});
setCharacters(charactersWithAllData);
};
const fetchApi = () => {
const characters = [];
Promise.all(
[api].map((api) =>
fetch(api)
.then((response) => response.json())
.then((data) => characters.push(...data.results))
.then((data) => fetchOtherData(characters))
.then(setFetched(true))
)
);
};
fetchApi();
}, []);
Thanks for all the of the possible replies.
You can add a loading state, which by default is set to true. After you finish fetching the data from the api you can set loading to false and then render the page.
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchOrdersData = Promise.all().then((data) => {
//Fetch all data
.....
//Render page after loading is false
setLoading(false);
}).catch()
}, [])
Issue
Found a few issues:
Primary Issue: The fetchApi function's Promise.all chain was immediately invoking setFetched(true) instead of waiting for the chain's thenable to invoke it. This is why you are seeing the empty table before the data has been fetched and processed.
const fetchApi = () => {
const characters = [];
Promise.all(
[api].map((api) =>
fetch(api)
.then((response) => response.json())
.then((data) => characters.push(...data.results))
.then((data) => fetchOtherData(characters))
.then(setFetched(true)) // <-- immediately invoked
)
);
};
fetchOtherData function is also synchronous and doesn't return a Promise so the outer Promise-chain in fetchApi doesn't wait for the processing to complete, this is why you still see URL strings instead of the resolved values from the fetch.
Solution
Fix the Promise chain in fetchApi to be invoked in the .then callback. Rework the logic in fetchOtherData to wait for the additional fetch requests to resolve.
Here's my suggested solution:
const api = `https://swapi.dev/api/people/`;
function App() {
const basicClassName = "starWarsApi";
const [characters, setCharacters] = useState([]);
const [fetched, setFetched] = useState(false);
useEffect(() => {
const fetchOtherData = async (characters) => {
const characterReqs = characters.map(async (character) => {
const { homeworld, species, starships, vehicles } = character;
const urls = [homeworld, ...species, ...vehicles, ...starships];
// Make shallow copy to mutate
const characterWithAllData = { ...character };
const urlReqs = urls.map(async (url) => {
if (url.length) {
try {
const response = await fetch(url);
const { name } = await response.json();
if (url.includes("species")) {
characterWithAllData.species = name;
}
if (url.includes("planets")) {
characterWithAllData.homeworld = name;
}
if (url.includes("vehicles")) {
characterWithAllData.vehicles.shift();
characterWithAllData.vehicles.push(name);
}
if (url.includes("starships")) {
characterWithAllData.starships.shift();
characterWithAllData.starships.push(name);
}
} catch (err) {
console.error(err);
}
} else {
if (url.includes("species")) {
characterWithAllData.species = "Unspecified";
}
if (url.includes("vehicles")) {
characterWithAllData.vehicles = "";
}
if (url.includes("starships")) {
characterWithAllData.starships = "";
}
}
});
await Promise.all(urlReqs); // <-- wait for mutations/updates
return characterWithAllData;
});
return Promise.all(characterReqs);
};
const fetchApi = () => {
Promise.all(
[api].map((api) =>
fetch(api)
.then((response) => response.json())
.then((data) => fetchOtherData(data.results))
.then((charactersWithAllData) => {
setCharacters(charactersWithAllData);
setFetched(true);
})
)
);
};
fetchApi();
}, []);
return (
<div className={basicClassName}>
{fetched && (
<>
<h1 className={`${basicClassName}__heading`}>Characters</h1>
<div className={`${basicClassName}__inputsAndBtnsSection`}>
<FilteringSection
basicClassName={`${basicClassName}__inputsAndBtnsSection`}
/>
<ButtonsSection
basicClassName={`${basicClassName}__inputsAndBtnsSection`}
/>
</div>
<CharactersTable characters={characters} />
</>
)}
</div>
);
}

How do I use async/await with Array.filter properly in React?

I'm creating just a simple currency converter (React + Typescript). Here is my component code:
const App = () => {
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
for (let i in currencies) {
if(i === country.currencies[0].code) {
return country
}
}
})
setCountries(filtered)
}
useEffect(() => {
axios
.get('https://api.frankfurter.app/currencies')
.then(res => {
setCurrencies(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
useEffect(() => {
filteredCountries()
}, [])
return (
...
)
}
export default App
I come across the problem, during launching the app. After getting currencies information from the server I need to fetch countries information. After getting countries I need to filter them and put them in my state (countries) and send it to another component and so on. But during launch of the app filter function doesn't work and I got no filtered countries and so I don't have any info in my state. I think that filter function needs to be an asynchronous, so we need to wait before setting our state through setCountries function. How to do it properly in my case or I did all the logic wrong?
As long as requested countries rely on fetched currencies and you don't seem to be using one without the another, you may stack .get()-requests accordingly or use respective async...await alternative:
fetchData = async () => {
const currenciesResponse = await axios.get(currenciesEndpoint),
currenciesData = await currenciesResponse.data,
countriesResponse = await axios.get(countriesEndpoint),
countriesData = await countriesResponse.data,
filteredCountriesData = countriesData.filter(_country => {
const {
currencies: [{ code }]
} = _country;
return currenciesData[code];
});
setCurrencies(currenciesData);
setCountries(filteredCountriesData);
}
useEffect(() => {
fetchData();
}, [])
Following is a full-blown demo as a proof-of-a-concept
See if this helps.
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
return currencies[country.currencies[0].code]
})
setCountries(filtered)
}
useEffect(() => {
axios
.get('https://api.frankfurter.app/currencies')
.then(res => {
setCurrencies(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
useEffect(() => {
filteredCountries()
}, [currencies])
try using this:
const App = () => {
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const res = await axios.get('https://api.frankfurter.app/currencies')
// you don't need a state for currencies but in case you find a use case for it,
// you're just setting the currencies here for future use cases.
setCurrencies(res.data);
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
for (let i in res.data) {
if(i === country.currencies[0].code) {
return country
}
}
})
setCountries(filtered)
}
useEffect(() => {
filteredCountries()
}, [])
return (
...
)
}
export default App

Categories

Resources