koubin
koubin

Reputation: 607

wait for multiple API calls to finish using useAxios custom hook

I use useAxios custom hook to avoid the code duplicity as the API I am calling is always the same.

import { useState, useEffect } from 'react';
import rest from './rest';     // inside there is a definition of axios.create baseURL...       

const useAxios = (endpoint,queryString) => {

  const [axiosData, setAxiosData] = useState(null);
  const [axiosLoading, setAxiosLoading] = useState(true);

  useEffect(() => {

    const getRest = async () => {
      setAxiosLoading(true);
      try{
        const sensors  = await rest
        .get('/'+ endpoint + '?' + queryString)
        .then(res => {
          setAxiosData(res.data);
          setAxiosLoading(false);
        });
      }catch (e) {
        console.log(e);
      }
    }

    getRest(); 

  }, [endpoint,queryString])

  return {axiosData, axiosLoading}; 

}
export default useAxios;

Here is a part of the code where I call useAxios hook.

const {axiosData:data1} = useAxios('request1','');
const {axiosData:data2} = useAxios('request2','querystring1');
const {axiosData:data3} = useAxios('request3','querystring2');

The problem I am trying to solve is how to wait for all the requests to finish, so it is for sure that all the data are ready.

I know about the axios.all and would like to use something like that, but with usage of the reusable code like with useAxios.

Side note: there are many useAxios calls through the app, not just the one case I explain here. That is the reason I am trying to reuse it instead using one axios.all

EDIT: I might have mentioned that earlier, but there is also another useAxios (data4) call in subcomponent which uses data from data1, data2, data3 and should render another subcomponent as many times as the number of objects contained in data1. I am passing the data through Context API and need to make sure the data1, data2, data3 are available before useAxios (data4) call

Upvotes: 1

Views: 1609

Answers (2)

Dave
Dave

Reputation: 7717

Obviously you could do something as simple as

if (!data1 && !data2 && !data3) {
  // do something
}

Since that code will run any time one of those stat variables changes. Or you could put them all as dependencies on a useEffect().

I suspect folks might steer you toward using a state machine if things are getting complicated. See useReducer()

Update based on new information You're probably aware, hooks must be called in the same order every time. You wrote this question because you're dealing with a code smell and I suspect the most 'correct' answer I can give is to think about how you can refactor the code. Break things up into smaller units and make each unit be responsible for less.

The 'quick bandaid' is to update useAxios() so you can no-op it:

const useAxios = (endpoint,queryString,bornReady=true) => {
...
if (bornReady) {
  getRest(); 
}
...

By defaulting the new parameter to true, old code will sail on by, but you can use the flag to prevent the hook from running. Here I use the querystring as the flag as well. When you can build the querystring, you're ready to make the rest call. Before that, do nothing.

const buildQuery = (data, moreData, soMuchData) => {
  if (!data || !moreData || !soMuchData) {
    return undefined // no queryString for you
  }
  // build and return your query
  return `c=${data.custId}&p=${moreData.numThings}`
}
const {axiosData:data1} = useAxios('request1','');
const {axiosData:data2} = useAxios('request2','querystring1');
const {axiosData:data3} = useAxios('request3','querystring2');
const querystring3 = buildQuery(data1, data2, data3)
const {axiosData:data4, axiosLoading} = useAxios('request4',querystring3, !!querystring3);

Do a refactor if you can. Maybe even move those calls to the server so you can make 1 request that returns only the data you need. If you're in a jam, add to the mess as illustrated above.

Upvotes: 1

jsejcksn
jsejcksn

Reputation: 33701

Don't destructure the axiosData property from each object returned by the hook. That way, you can inspect the axiosLoading property on every dataN value to make a determination of whether or not they're all done, like this:

const data1 = useAxios('request1','');
const data2 = useAxios('request2','querystring1');
const data3 = useAxios('request3','querystring2');

const done = [data1, data2, data3].every(({axiosLoading}) => !axiosLoading);
if (done) {
  // they're all done loading
}
else {
  // at least one is still loading
}

Upvotes: 1

Related Questions