How to handle asynchronous code and pass it down to props?

Viewed 43

So I'm working on an app, and in the app there's a component that handles a bunch of data mutations. I'll explain it in pseudocode for brevity and link the full code sample below.

function ({ API_DATA }) {

let dataHandoff1 = []

function mutateData = async () => {
    // perform mutation on API_DATA (filter, map, slice etc)
    dataHandoff1.push(finished mutation)
    }

function mutateData2 = async () => {
    // perform another mutation on dataHandoff1
    dataHandoff2.push(dataHandoff1)
}

function forceFunctionsToRunOneAfterTheOther = async () => {
    const a = await mutateData
    const result = await mutateData2
    return finished 
    console.log(finished) //This works; it displays the data I want
}    

const finalData = useMemo(() => forceFunctionsToRunOneAfterTheOther(), []);

return (
    <ChildComponent finalData={finalData} />
)

My goal was:

  1. I need to make sure each of the functions waits for the others to finish before the next one runs, since they're based off of data that needs to be mutated by the previous function. To accomplish this I need to make them async.

  2. Once the data mutations are complete, I want that final received data to persist between renders. I don't want to use a useEffect hook since it can force the component to re-render and make the calculations run again. To accomplish this I set the data to a memo.

However, the issues I'm running into are:

  1. when I look in the dev tools, "finalData" is still a promise, although by having forceFunctionsToRunOneAfterTheOther to return "finished" (which I verified is indeed the data I want by console logging) I thought it would have access to the actual values. I initially wanted to have the forceFunctionsToRunOneAfterTheOther function to work inside of a useEffect hook, but in order to make it's final value accessible outside of the function I would need to use a useState call, which would also force the component to rerender and the mutations would have to be done all over again. How can I set up this logic to work?

  2. I want to pass down the final data of the mutation as a prop, which is technically working, however the prop is being passed down as empty, and my app isn't checking again to see if the prop changed (it seems). I tried to conditionally render the child component, but it doesn't seem to be working. How can I go about this?

Full code sample:

import React from 'react';
import TopSongsList from './TopSongsList';
import { useEffect, useState, useMemo } from 'react';



function TopSongs({ songList }) {

  const [readyToRender, setReadyToRender] = useState();

  const fullSongList = []
  const songArray = []
  const songFineDetails = []
  let removedSongDetailsDuplicates = []
  let top100Songs 

  const getSongs = async () => {
    for (let i = 0; i < songList.length; i++) {
      fullSongList.push(songList[i].data.items);
      return fullSongList
    }
  }

  const getSongs2 = async () => {
    for(let j = 0; j < fullSongList.length; j++) {
    fullSongList[j].forEach(songs => songArray.push(songs));
    return songArray
    }
  }

  const getSongs3 = async () => {
   for (let k = 0; k < songArray.length; k++) {
    const {artists, external_urls, id, name, popularity } = songArray[k].track;
    songFineDetails.push({"artists": artists,
                          "external_urls": external_urls, 
                          "id": id, 
                          "name": name, 
                          "popularity": popularity
                          });
      }
      return songFineDetails
    }
  
  const getSongs4 = async () => {
    removedSongDetailsDuplicates = [...new Map(songFineDetails.map(v => [v.id, v])).values()];
    removedSongDetailsDuplicates.sort((a, b) => b.popularity - a.popularity);
    top100Songs = removedSongDetailsDuplicates.splice(0, 100);
    return top100Songs
  }
  const finishedSongs = async () => {
    const a = await getSongs(); 
    const b = await getSongs2(); 
    const c = await getSongs3(); 
    const finished = await getSongs4();
    setReadyToRender(1)
    return finished
  }

  

 const songData = useMemo(() => finishedSongs(), []);

  return (
    <div>
      {readyToRender > 0 && <h1><TopSongsList songData={songData}/></h1>}
    </div>
  )
}

export default TopSongs
1 Answers

Split the data loading into a useState and a useEffect. You have to use useState in order to persist data across renders. In your version of the code, your fullSongList, songArray, etc. variables are available only to the first render of the component.

function TopSongs({ songList }) {

  const [songData, setSongData] = useState(null);
  useEffect(
    () => {
      const fullSongList = []
      const songArray = []
      const songFineDetails = []
      let removedSongDetailsDuplicates = []
      let top100Songs 

      const getSongs = async () => {
        for (let i = 0; i < songList.length; i++) {
          fullSongList.push(songList[i].data.items);
          return fullSongList
        }
      }

      const getSongs2 = async () => {
        for(let j = 0; j < fullSongList.length; j++) {
        fullSongList[j].forEach(songs => songArray.push(songs));
        return songArray
        }
      }

      const getSongs3 = async () => {
      for (let k = 0; k < songArray.length; k++) {
        const {artists, external_urls, id, name, popularity } = songArray[k].track;
        songFineDetails.push({"artists": artists,
                              "external_urls": external_urls, 
                              "id": id, 
                              "name": name, 
                              "popularity": popularity
                              });
          }
          return songFineDetails
        }
      
      const getSongs4 = async () => {
        removedSongDetailsDuplicates = [...new Map(songFineDetails.map(v => [v.id, v])).values()];
        removedSongDetailsDuplicates.sort((a, b) => b.popularity - a.popularity);
        top100Songs = removedSongDetailsDuplicates.splice(0, 100);
        return top100Songs
      }

      const finishedSongs = async () => {
        const a = await getSongs(); 
        const b = await getSongs2(); 
        const c = await getSongs3(); 
        const finished = await getSongs4();
        // Write to a `useState` variable to persist the data across renders
        setSongData(finished);
      }

      finishedSongs();
    },
    // This should run only one time on the component's first render.
    []
  );

  return (
    <div>
      {songData != null && <h1><TopSongsList songData={songData}/></h1>}
    </div>
  )
}
Related