Dependent Requests in React Query
This is the second post in a series of posts on React Query with TypeScript. In the last post, we did a basic web service request using the useQuery
hook. This post will expand this example and make a second request that requires data from the first request.
Our requirement
At the moment, our React component requests the people
resource in the Star Wars API and displays the character’s name.
This is the component at the moment:
export default function App() {
const { status, error, data } = useQuery<Character, Error>(
["character", { id: 1 }],
getCharacter
);
if (status === "loading") {
return <div>...</div>;
}
if (status === "error") {
return <div>{error!.message}</div>;
}
return data ? <h3>{data.name}</h3> : null;
}
There is also a homeworld
field in the response, which is a URL to get information about the character’s home planet.
{
"name": "Luke Skywalker",
...
"homeworld": "https://swapi.dev/api/planets/1/", ...
}
We require to make a request to this URL and display the home planet name for the character alongside their name.
Second fetching function
We will start by creating the fetching function for getting the home planet:
async function getPlanet({
queryKey,
}: {
queryKey: [string, { url: string }];
}) {
const [, { url }] = queryKey;
const response = await fetch(url);
if (!response.ok) {
throw new Error("Problem fetching planet");
}
const data = await response.json();
assertIsPlanet(data);
return data;
}
The query key is passed into the function, which is a tuple with the second element containing the URL that should be used to make the request.
The function makes a simple request and raises an error if unsuccessful.
The type of the data is asserted to be Planet
with the assertIsPlanet
type assert function:
type Planet = {
name: string;
};
function assertIsPlanet(data: any): asserts data is Planet {
if (!("name" in data)) {
throw new Error("Not home planet");
}
}
Second query
Let’s now add a second query in the component using the useQuery
hook:
import { useQuery } from 'react-query'
export function App() {
const {
status: characterStatus,
error: characterError,
data: characterData,
} = useQuery<Character, Error>(["character", { id: 1 }], getCharacter);
const { status: planetStatus, error: planetError, data: planetData, } = useQuery<Planet, Error>(["planet", { url: characterData?.homeworld }], getPlanet); ...
}
We have aliased the state variables so that they don’t clash.
We’ve also passed the homeworld
field from the character response data into the getPlanet
fetching function.
Now let’s do the rendering:
export default function App() {
...
if (characterStatus === "loading" || planetStatus === "loading") {
return <div>...</div>;
}
if (characterStatus === "error") {
return <div>{characterError!.message}</div>;
}
if (planetStatus === "error") {
return <div>{planetError!.message}</div>;
}
return (
<div>
{characterData && <h3>{characterData.name}</h3>}
{planetData && <p>{planetData.name}</p>}
</div>
);
}
The component compiles ok, which is great, and the correct character and planet name will be rendered:
However, if we look in the DevTools network panel, we will see that a planet request was made before the character response was received. So, a request is made to an undefined
URL:
When a character response is received, a rerender occurs and a second call to getPlanet
happens, and a valid request is made.
Executing the second query when the first has returned
Although the correct data is rendered, we want to stop that first call to getPlanet
.
We resolve this problem with an enabled
option in useQuery
. This is set to a boolean expression. When this is set, the query will only run when it is evaluated to true
.
We can use enabled
option and set this to whether the homeworld
property in the character data has a value:
const {
status: planetStatus,
error: planetError,
data: planetData,
} = useQuery<Planet, Error>(
["planet", { url: characterData?.homeworld }],
getPlanet,
{ enabled: !!characterData?.homeworld, });
We now only get a single request for the planet data.
Nice. 😊
The code in this post is available in CodeSandbox at https://codesandbox.io/s/dependent-query-1y89q?file=/src/App.tsx.
Did you find this post useful?
Let me know by sharing it on Twitter.If you to learn more about using TypeScript with React, you may find my course useful: