Can react-query make sequential network calls and wait for previous one to finish?

1.5k views Asked by At

In the following code for example, how can I update react-query to wait for the previous call to be complete before starting the next one?

I'm trying to hit an API that has rate limiting to one call at a time but I have a list of calls I need to make, I get a 429 if more than one are done at the same time.

const [selection, setSelection] = React.useState([])

const results = useQueries(
  selection.map(item => ({
    queryKey: ['something', item]
    queryFn: () => fetchItem(item)
   })
)

const data = results.map(result => result.data)
2

There are 2 answers

1
Anton On

I'm not aware of a built-in solution.

But how about this. A state that counts queries that have been run, and enables the next query.

You might need staleTime: Infinity to make sure the library doesn't refetch all queries at the same time.

const [selection, setSelection] = React.useState([])
const [noCompleted, setNoCompleted] = React.useState(0)

const results = useQueries(
  selection.map((item, i) => ({
    queryKey: ['something', item]
    queryFn: () => fetchItem(item)
    enabled: i <= noCompleted
    staleTime: Infinity
    cacheTime: Infinity
   })
)

const firstLoading = results.findIndex((r) => r.isLoading)

React.useEffect(() => {
  setNoCompleted(firstLoading)
}, [firstLoading])
0
DavidP On

Exactly as Anton suggested, React Query exposes a method called enabled. to illustrate it for you, use normal methods to loop through your items, and then only call RQ when the previous query finished, and then toggling the enabled state. This is a very rough, more verbose example, and running replit here.

I also included how to reuse the cache in the replit, so let me know if it makes sense. It can most definitely be optimised better, and the setTimeout is only to show you visually the progress.

async function getCoins(coin: string): Promise<APIResponse> {
  try {
    const response = await fetch(`https://api.gemini.com/v2/ticker/${coin}usd`);
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error("There was a problem with the fetch operation:", error);
    throw error;
  }
}

export function useCoin(coins: string[]) {
  const [currentIndex, setCurrentIndex] = useState(0);
  const [allData, setAllData] = useState<APIResponse[]>([]);

  const query = useQuery(['coinData', coins[currentIndex]], () => getCoins(coins[currentIndex]), {
    enabled: currentIndex < coins.length,
    onSuccess: (data) => {
      setTimeout(() => {
        setAllData(prev => [...prev, data]);
        setCurrentIndex(prev => prev + 1);
      }, 1500)
    }
  });
  return { ...query, data: allData };
}

export function CachedEntries() {
  const [eth, setEth] = useState<APIResponse | null>(null)
  const cached = queryClient.getQueryData<APIResponse>(['coinData', 'eth'])

  useEffect(() => {
    if (cached) {
      setEth(cached);
    }
  }, [cached]);

  return eth !== null ? (
     <div>Entries from cache:
        <div key={eth?.symbol}>{eth?.symbol}: {eth?.close}</div>
      </div>
  ) : null
}

export function Entries() {
  const { data, error, isError } = useCoin(['btc', 'eth']);

  if (isError) console.log(error)

  return (
    <div>
      <div>Entries from query:
        <div>{data?.map(coin => (
          <div key={coin.symbol}>{coin.symbol}: {coin.close}</div>
        ))}</div>
      </div>
      <CachedEntries />
    </div>
  )
}

In the example from Anton, he uses the built in useQueries which would be a better approach. Hopefully between the two answers you can understand how to use RQ, how it works, and cool things you can do with it.