Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Dependent Requests in React Query

March 10, 2021
reacttypescript

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.

Dependent Requests in React Query

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:

Render

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:

Undefined request

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.
Click here to share this post on Twitter

If you to learn more about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Find out more