skip to Main Content

I have two arrays in state:

const [offers, setOffers] = useState([]);
const [redemptions, setRedemptions] = useState([]);

I am updating the properties within this function below:

useEffect(() => {
  getData();
}, []);

const getData = () => {
  Promise.all([getVenue(), getUserData(), getOffers(), getRedemptions()])
    .then(() => {
      console.log("OFFERS", offers); // HERE ARE THE OFFERS: [] ??
      console.log("REDEMPTIONS", redemptions); // HERE ARE THE REDEMPTIONS: [] ??
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {
      setLoading(false);
    });
};

const getOffers = async () => {
  const offers = await fetchOffers(id);
  setOffers(offers);
};

const getRedemptions = async () => {
  const redemptions = await fetchRedemptions(user.uid, id);
  setRedemptions(redemptions);
};

However, after all the functions have completed I need access to offers and redemptions and my console logs are returning empty arrays, although my UI is updating with the correct values.

What am I missing here?

2

Answers


  1. Just update the code accordingly!

    const getOffers = async () => {
      const offers = await fetchOffers(id);
      setOffers([...offers]);
    };
    
    const getRedemptions = async () => {
      const redemptions = await fetchRedemptions(user.uid, id);
      setRedemptions([...redemptions]);
    };
    

    This will create a shallow copy of the response and set the new value to the state.

    Login or Signup to reply.
  2. Notice how offers and redemptions are const. The values of these don’t change in the current execution of your component/function when you call the setXYZ() functions. Their values only "change" when your function rerenders/executes again and recreates the variables for that new execution of your component/function. It’s the same reason why if you have a function, foo, the variables created inside foo() are scoped to that execution, and won’t change if you happen to call foo() again

    function foo(call) {
      const bar = Math.random();
      console.log(`bar for fucntion call ${call} is`, bar);
      setTimeout(() => { // "fake" 
        console.log(`After timeout, bar in ${call} is`, bar);
      }, 1000);
    }
    
    foo(1);
    foo(2); // This doesn't change the value of `bar` in the first functioon call

    You can find out more information on this in the question – The useState set method is not reflecting a change immediately

    What you really should do if you want to access offers and redemptions in your .then() callback is have getOffers() and getRedemptions() return promises which resolve to the offers and redemptions values respectively. You actually already have functions which do this, fetchOffers() and fetchRedemptions(). This way, you can remove the getOffers() and getRedemptions() functions and call your state setter function in your .then() callback (note: if you don’t want to call them in the .then() callback you can keep what you have now, but return the offers and redemptions values from your async functions). This has the added advantage keeping your .then() from firing after you’ve rendered and using values that might no longer be relevant.

    Promise.all([getVenue(), getUserData(), fetchOffers(id), fetchRedemptions(user.uid, id)])
        .then(([venue, userData, offers, redemptions]) => { // destructure the resolved values from each promise
          console.log("OFFERS", offers);
          console.log("REDEMPTIONS", redemptions);
          // My suggestion here is to perform the setting in the `.then()` so that your callback logic that uses the offers and redemptions values are the same values which the user sees via the state values:
          setOffers(offers);
          setRedemptions(redemptions);
        })
    

    Also, you should consider using a cleanup function as part of your useEffect() that provides the capability to cancel outgoing requests to avoid trying to update the state of a component when it no longer exists.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search