We need a function that may call an API or the other, and based on the result, call yet another API.
Let me give a simplified example. We're trying to do something like this
function getCarDetails(car: CarSummary): UseQueryResult<CarDetails> {
if(car.make === 'honda') {
const hondaResponse = useHondaData(car);
if(hondaResponse.isError)
return hondaResponse;
return useCarDetailsData(
{x: hondaResponse.data?.y},
{enabled: !!hondaResponse.data}
);
}
else if(car.make === 'tesla') {
const teslaResponse = useTeslaData(car);
if(teslaResponse.isError)
return teslaResponse;
return useCarDetailsData(
{x: teslaResponse.data?.y},
{enabled: !!teslaResponse.data}
);
}
else {
return useCarDetailsData(
{x: 'generic'},
{enabled: true}
);
}
}
The above code would be ideal, if only hooks can be called from inside if
s.
But as hooks can only be called from the top level, what is the best way to achieve the above (i.e. nested/conditional chained API calls using React Query)?
By adding an enabled
flag to useHondaData
and useTeslaData
we can do like below.
Here we always call the hooks, but disable the ones that are not neeeded. Before return
we check if the first call produced an error, in that case we return that first response, otherwise we return the response from useCarDetails
.
function getCarDetails(car: CarSummary): UseQueryResult<CarDetails> {
// always call the hooks but disable the ones not needed with an `enabled` flag
const hondaResponse = useHondaData(car, { enabled: car.make === 'honda' });
const teslaResponse = useTeslaData(car, { enabled: car.make === 'tesla' });
// ignore the types here
let response: ReturnType<typeof useHondaData> | ReturnType<typeof useTeslaData> | undefined
let x: NonNullable<ReturnType<typeof useHondaData>['data']>['y'] | NonNullable<ReturnType<typeof useTeslaData>['data']>['y'] | undefined
if (car.make === 'honda' || car.make === 'tesla') {
// save the appropriate response for later use
response = car.make === 'honda' ? hondaResponse : teslaResponse
x = response.data?.y
} else {
x = 'generic'
}
// always call this hook
const details = useCarDetailsData(
{ x },
// disable it if we don't have the data we need (x), or if we have an error
{ enabled: !!x && !response?.isError }
);
// if we have an error, return the first response, otherwise return the details
return response.isError ? response : details
}
Note that the approach as described in your question would have getCarDetails
return the output of useCarDetails
while useHondaData
or useTeslaData
are loading, but when one of those fails, it would return the output of useHondaData
or useTeslaData
. This might cause confusion and issues. It would be better if we returned a custom object from getCarDetails
.
E.g. (depending on the needs)
return {
data: details.data,
error: response.error ?? details.error,
isLoading: response.isLoading || details.isLoading
}
Also note that since getCarDetails
uses hooks, it is itself a hook and it's name should start with use
.