Access variable outside useEffect in React.js - javascript

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)

Related

Is there a way to make an api call within a map of another api call?

I know the title is quite confusing, I wasn't sure how to word it better. What I am trying to do is to fetch some items, map through those items to display them, but the problem is that one of those items has a value of what needs to be another api call to access it.
This is what I'm trying to do:
First of all I am storing an empty state, which later on becomes the data of the fetched items:
const [data, setData] = useState([]);
I'm using axios to fetch and store the data:
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then((response) => {
console.log(response.data.results);
const newData = response.data.results.map((item) => ({
name: item.name,
homeworld: () => {
axios.get(item.homeworld).then((response) => {
response.data.results;
});
},
}));
setData(newData);
})
.catch((error) => {
console.log("error", error);
});
};
It works with the name because it's a simple value. However, the homeworld includes a link that needs to be called once again in order to access it, instead of being a simple value like the name in this case. How can I call it and access what values are held within that link, and display them instead of just displaying the url?
I hope this can help you:
const [data,setData] = useState([])
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then(response => {
console.log(response.data.results);
const { results } = response.data;
for (const item of results) {
axios.get(item.homeworld).then(({data}) => {
setData([...data,{ name: item.name, homeworld: data.results }]);
});
}
})
.catch(error => {
console.log("error", error);
});
};
or with fetch:
const [data,setData] = useState([])
fetch("https://swapi.dev/api/people").then(re=>re.json())
.then(response => {
const newData = []
const { results } = response;
const newData = [];
for (const item of results) {
fetch(item.homeworld).then(re => re.json()).then((data) => {
newData.push({ name: item.name, homeworld: data });
});
}
console.log(newData)
setData(newData)
})
.catch(error => {
console.log("error", error);
});
Use Promise.all()
You can use Promise.all() method to get all the information you need by creating an array of promises by mapping the response.results array with an async function.
This is the code example
const fetchItems = async () => {
const req = await axios.get("https://swapi.dev/api/people");
const response = await req.data;
const allDataPromises = response.results.map(async (item) => {
const itemReq = await axios.get(item.homeworld);
const itemResponse = await itemReq.data;
return {
name: item.name,
homeworld: itemResponse,
};
});
const allData = await Promise.all(allDataPromises);
};
For further information about Promise.all()

How to wait async data to start sync function

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 => {..});
;
}, []);

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.

Get data from Firebase with React

function Editor({ userObj }) {
const [myContents, setMyContents] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const db = dbService.collection("contents").onSnapshot((snapshot) => {
if (snapshot.size) {
const communityArray = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setMyContents(communityArray);
setLoading(false);
console.log(loading);
} else {
setLoading(false);
}
});
return () => {
db();
};
}, []);
setTimeout(() => {
console.log(loading);
console.log("4", myContents[0].id);
}, 1000);
I don't know why the message appears so many times.
When I print console.log(loading) after setLoading(false), I don't know why it shows true.
When using the setTimeout function, the id value is displayed after an error.
Is it necessary to use async for the id to come out properly?
You can check React.StrictMode in app.js or index.js, it will duplicate render. You can try remove it.
You want it run in your code, pls check it has a value before log
console.log("4", myContents[0] && myContents[0].id);
After that useEffect run, it will re-call setTimeout when it call setMyContents(communityArray); setLoading(false);

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