2

I am really struggling to wrap my head around async api calls. I am trying to render some data from an api call in my react application. The structure is as follow: Essentially, my thought process is that I am using fetchYearlyData to return the data from an api call. Then that data is processed in percentileRank to return a value. The main function calls the fetchYearlyData and percentileRank function in order.

const fetchYearlyData = async (year, stat) => {
 let response = await fetch(
 `http://localhost:5000/api/yearly_data/${year}/${stat}`
  );
 return response.json();
};
const percentileRank = (data, stat, percentile) => {
 data = data.map((item) => {
 return parseFloat(item[stat]);
  });
 let sortedArray = data.sort((a, b) => a - b);
 let rank = Math.floor((percentile / 100) * (sortedArray.length + 1));
 return sortedArray[rank];
};
export const main = async (year, stat, rank) => {
 // Make API Call
 let data = await fetchYearlyData(year, stat);
 // Return data array
 return percentileRank(data, stat, rank);
};

So far there are no issues.

But when I try and bring this into my react component for graphing purposes, I seem to be getting the data back as a promise instead of actual values, which prevents the graph from forming.

const data = {
 labels,
 datasets: [
      {
 label: "25th percentile",
 data: labels.map(async (year) => {
 let result = await main(year, props.statSelection, 25);

 return result;
        }),
      },
    ],
  };

return (
 <div id="chart">
 <Line options={options} data={data} />
 </div>
  );

I am completely lost, in that I am using async and await and still keep seeing promises, which is very frustrating. I would forever be in your debt if someone could explain this to me :)

Thanks!

user4081147
  • 75
  • 1
  • 7

2 Answers2

1

so basically this is what is happening:

  • you have component 'X'
  • inside component 'X' you create a const data which uses an async function within it to generate some properties etc. Remember its async, but your react component is not. So the 'Promise' of this async function would not have resolved by the time your return statement starts rendering on your screen. So ultimately when your <Line/> component is rendered, data is still a pending Promise` and not really actual data.

There are a few other complexities to why your code is not working, one of them being the async await inside a .map which needs to be dealt with in a whole other way, using something called Promise.all, but what i have tired to explain above, is more of a concept on why your code is not working.

I'll try to give a simplified example that should explain the correct 'pattern' to do what you are doing:


import main from '../<some path>' //your current async function. assuming it does what its supposed to be doing

//this is how you deal with async in map.

const genFullData = async (labels, callback) => {
   let promises = labels.map(async (year) => {
   let result = await main(year, props.statSelection, 25);
   return result;
  }),

  let dataResults = await Promise.all(promises);

  let fullData = {
    labels,
    datasets: [
     {
       label: "25th percentile",
       data: dataResults
     },
    ],
  };

  callback(fullData);
}

const X = props => { //react comp

  const [data, setData] = useState(undefined)
  
  useEffect(() => {
   
   genFullData(
     props.labels, 
     (fullData) => setData(fullData)
   )

  },[props.labels])
  
  return (
    !data ? <h3>Loading...</h3> : <Line data={data} />
  )

}


So ill explain the different parts of the above code:

  • First the async genFullData function. 2 things to notice. 1// see how async inside .map has to be dealt with using something called Promise.all read up on it. But in essence, Promise.all ensures that the function 'waits' until all the promises in the .map loop are resolved (or rejected), and only then moves on with the code. 2// Second thing to notice is, how in the end, once the Promises are done with, i take the results, shove them into the data structure that i picked up from your code, and then i feed this 'final data' to a callback function, which you'll understand in a bit.

  • Now lets look at the react component. //1 have initiated a local data state with a value of undefined //2 Inside a useEffect, I fire the genFullData function, and in the callback that i pass to it, i update the data state to whatever the genFullData function returns.

  • Finally the return statement of the comp renders a 'loading..' if the data state is undefined, and the minute the data state becomes 'defined', then the Line comp is rendered with the appropriate data being passed to it.

This last bit can be confusing so ill try to summarize again.

  1. comp renders for the first time.
  2. Init data state is set to undefined
  3. genAllData function is fired in a useEffect (which fires only once). This is an sync function, so it takes sometime till it actually manages to get the data.
  4. In the meantime, the react component continues rendering (remember, data has not been fetched yet by genAllData)
  5. React hits the return statement, where it checks to see if data is 'defined'. Well it is not, so it renders 'Loading..' on screen
  6. In the meantime, genAllData manages to successfully complete its operations, and the callback function is fired.
  7. In this callback, we setData to whatever genAllData is providing to the callback.
  8. This setData triggers the component to rerender.
  9. return statement fires again.
  10. this time data IS 'defined', so the Line comp is rendered, with the appropriate data being passed into it.
Abrar Burk
  • 156
  • 8
0

Got a promise? Get it done with .then(res => { //your code here })