Reputation: 742
I am struggling to figure out how to fire my moveToResults()
function once the setArr(list)
is filled. I feel like there should be a simple way to do this with a new Promise
, but I cannot seem to figure it out.
const [arr, setArr] = useState([]);
const handlePress = ()=>{
var list = [];
firebase.firestore().collection('PostedFunEvents').where("location", "==" , place).get().then((querySnapshot) =>{
querySnapshot.forEach((doc) =>{
list.push(doc.data());
})
setArr(list);
}).then(()=>{
console.log(arr.length);
}).then(()=>{
moveToResults(); // AT THIS POINT ARR.LENGTH IS STILL 0
});
}
And this is my moveToResults()
function:
const moveToResults = () =>{
navigation.navigate('Results', {arr: arr})
}
The question
How do I make sure moveToResults()
fires once setArr(list)
is all the way done?
I basically want to make sure that when moveToResults()
is fired arr
is populated. Otherwise I pass nothing to the next screen.
Upvotes: 1
Views: 329
Reputation: 516
Try this dude. setArr and navigation should be called only after the list is available.
const handlePress = async () => {
const myPromise = new Promise((resolve, reject) => {
const list = [];
firebase
.firestore()
.collection('PostedFunEvents')
.where("location", "==", place)
.get()
.then((querySnapshot) => {
querySnapshot.forEach(doc =>
list.push(doc.data()
));
resolve(list);
}).catch(reject);
});
try {
const myList = await myPromise();
console.log(myList);
setArr(list);
navigation.navigate('Results', {arr: myList});
} catch (e) {
console.log(e)
}
}
Upvotes: 0
Reputation: 25413
we can write customise function which will call the callBack function if any changes in the state
then you can fire function from callback
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const useStateCallbackWrapper = (initilValue, callBack) => {
const [state, setState] = useState(initilValue);
useEffect(() => callBack(state), [state]);
return [state, setState];
};
const callBack = state => {
console.log("---------------", state);
};
function App() {
const [count, setCount] = useStateCallbackWrapper(0, callBack);
return (
<div className="App">
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Upvotes: 0
Reputation: 436
The arr
variable will not get populated until your component re-renders, therefore you cannot call moveToResults
in the same promise chain. However, this should be a simple check when the arr
is populated and then call the function.
const [arr, setArr] = useState([]);
useEffect(() => {
if (arr.length > 0) {
moveToResults(arr);
}
}, [arr])
const handlePress = ()=>{
var list = [];
firebase.firestore().collection('PostedFunEvents').where("location", "==" , place).get().then((querySnapshot) =>{
querySnapshot.forEach((doc) =>{
list.push(doc.data());
})
// Use concat because it returns a new array which will allow react to detect a state change
setArr(arr.concat(list));
});
}
Upvotes: 0
Reputation: 282050
State updates are not reflected until a re-render actually happens. In class components, setState provides a callback which is called when the state update is complete, however in hook state updater there is no such callback provided
The way to handle such scenarios is to make use of useEffect and listen to the state change. However since you do not want this is to on initialRender, you need to disable the effect on initialRender
const [arr, setArr] = useState([]);
const initialRender = useRef(true);
useEffect(() => {
if(!initialRender.current) {
moveToResults()
} else {
initialRender.current = false;
}
}, [arr]) // Listening on arr change
const handlePress = ()=>{
var list = [];
firebase.firestore().collection('PostedFunEvents').where("location", "==" , place).get().then((querySnapshot) =>{
querySnapshot.forEach((doc) =>{
list.push(doc.data());
})
setArr(list);
})
}
Howeve the above solution only works if the arr is being updated by handlePress action only or you want to navigate anytime there is a change in array. If the array is being updated from elsewhere and you do not wish to perform navigation on every array change, you cannot use the above approach
The correct way to handle such scenarios is to not rely on state update but pass on the argument to moveToResults directly
const [arr, setArr] = useState([]);
const handlePress = ()=>{
firebase.firestore().collection('PostedFunEvents').where("location", "==" , place).get().then((querySnapshot) =>{
var list = [];
querySnapshot.forEach((doc) =>{
list.push(doc.data());
})
return list;
}).then((list)=>{
moveToResults(list);
});
}
const moveToResults = (list) =>{
navigation.navigate('Results', {arr: list})
}
By writing a function moveToResults
to accept an argument, you can use it anywhere and rely on the passing the updated state instead of waiting for it to use the value from state.
Upvotes: 1
Reputation: 71
Why don't use redux, that would help a lot in this situation?
Anyway I was thinking, something like this:
const [arr, setArr] = useState([]);
const handlePress = () => {
firebase.firestore()
.collection('PostedFunEvents')
.where("location", "==" , place)
.get()
.then(snapshot => {
let list = [];
if (snapshot.empty) {
throw new Error('No location here.');
}
snapshot.forEach(doc => {
list.push(doc);
});
if (snapshot.length === list.length) {
setArr(list)
}
})
.catch(err => {
console.log('Error getting documents', err);
});
}
I hope it helps, or at least that it works
Upvotes: 1
Reputation: 4600
Set state using the array returned by querySnapshot.docs
:
const [arr, setArr] = useState([]);
// This callback will be called after 'arr' get elements.
React.useEffect(() => {
if (arr.length > 0) {
moveToResults(arr); // go!
}
}, [arr]);
const handlePress = () => {
firebase.firestore().collection('PostedFunEvents').where("location", "==" , place).get()
.then((querySnapshot) => {
console.log(querySnapshot.docs); // debug array
setArr(querySnapshot.docs); // setting list
// moveToResults(querySnapshot.docs); // go!
});
}
Upvotes: 1
Reputation: 604
instead of using multiple .then()
blocks, you can use async await in your firebase operation completion, i.e
firebase.firestore().collection('PostedFunEvents').where("location", "==" , place).get().then(async (querySnapshot) =>{
await querySnapshot.forEach((doc) =>{
list.push(doc.data());
})
setArr(list);
})
.catch(err=>{
console.log(err)
})
Upvotes: 0