Fabrizio Botalla
Fabrizio Botalla

Reputation: 742

{React Native} Firing a function once State is up-to-date

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

Answers (7)

p2pdops
p2pdops

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

Muhammad Numan
Muhammad Numan

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

Long Nguyen
Long Nguyen

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

Shubham Khatri
Shubham Khatri

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

Marco Edoardo Duma
Marco Edoardo Duma

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

Maycon Mesquita
Maycon Mesquita

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

Imjaad
Imjaad

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

Related Questions