React state not setting Object from temporary variable

Viewed 135

I apologize, I know this is a common question but I just can't find the right answer. I'm using a functional component that gets a list of JSON objects from an API. The list data does not have all the information I need so I am iterating through a list of the provided IDs and fetching each 'show' JSON that DOES have the info I need. I then put those into an Array in state. It's messy, bit it works well enough. While I am iterating and fetching the individual 'show' JSON I have some math I want to do with some of the values. I'm putting that info into an object, but when I try to set that object to state it just doesn't work, even though I can log the object and it presents as expected.

  const getStashList = async () => {
        <Code to fetch list data>
    }

    useEffect(() => {
        getStashList()
    }, [])

const getFullStashData = (data) => {
        const tempWeightObj = {}
        let fullStashArray = []
        try {
            data.forEach(async (stashItem) => {
                const indStashResponse = await fetch(`<the api address>`, {
                    method: 'GET',
                    headers: {
                        Authorization: "Bearer " + access_token
                    },
                    mode: 'cors'
                })
                const indStashData = await indStashResponse.json()
                fullStashArray.push(indStashData.stash)

                const amountStashed = indStashData.total_yards

                let weightName = indStashData.weight_name
                Object.defineProperty(tempWeightObj, weightName, {
                    value: amountStashed,
                    writable: true
                })
                console.log(tempWeightObj) //This logs the expected object
            })
        } catch (err) {
            console.error(`Error in fetch: ${err.message}`)
        }
        setFullStash(fullStashArray) //This state is set as expected
        setWeightYards(tempWeightObj) //The state is still empty

    }

    useEffect(() => {
        getFullStashData(stashList)
    }, [stashList])
1 Answers

The body of the getFullStashData function is completely synchronous. It declares tempWeightObj and fullStashArray, runs the forEach synchronously, and enqueues the state updates. The async forEach loop callbacks then mutate the references closed over in function scope.

What you are seeing is the mutation.

I suggest a refactor that declares the getFullStashData function to be async, map the data argument to an array of fetch Promise objects, await the array of responses to map to another array of Promise objects this time returning the JSON data. You've now the fullStashArray array to update state. Iterator over this fullStashArray array to populate the properties of the tempWeightObj object. Once all is complete then enqueue the state updates.

const getFullStashData = async (data) => {
  try {
    const reqs = data.map((stashItem) =>
      fetch(`<the api address>`, {
        method: "GET",
        headers: {
          Authorization: "Bearer " + access_token
        },
        mode: "cors"
      })
    );
    const responses = await Promise.all(reqs);
    const fullStashArray = await Promise.all(responses.map((res) => res.json()));

    const tempWeightObj = {};
    fullStashArray.forEach(
      ({ total_yards: value, weight_name: weightName }) => {
        Object.defineProperty(
          tempWeightObj,
          weightName,
          { value, writable: true }
        );
      }
    );

    setFullStash(fullStashArray);
    setWeightYards(tempWeightObj);
  } catch (err) {
    console.error(`Error in fetch: ${err.message}`);
  }
};
Related