Carl Rippon

Building SPAs

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

Getting Started with React Query and TypeScript

March 03, 2021
reacttypescript

This post covers how we can use React Query with TypeScript to fetch data. We’ll also cover some of the benefits React Query brings.

Getting Started with React Query and TypeScript

What is React Query?

React Query is a fantastic library that helps us manage data involved in web service requests.

It doesn’t make the actual request - we still use fetch or a library like axios to do this.

React Query will call our code that makes the request at the appropriate time in the component lifecycle. It also puts the data from the request in state and provides other useful state variables for the fetching process.

On top of this, React Query provides a ton of features such as caching and retries, but the thing I love about React Query is that it cleans up our code.

Installing React Query

To install React Query, we run the following command in a terminal:

npm install react-query

This package already includes TypeScript types. So, no separate install for these. 😊

Using React Query

React Query requires a QueryClientProvider component above the components that need to fetch data:

import { QueryClient, QueryClientProvider } from "react-query";
...

const queryClient = new QueryClient();
render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  rootElement
);

QueryClientProvider takes in an instance of the QueryClient class from the react-query package.

In a component that fetches data, a useQuery hook from the react-query package allows us to specify our function that fetches data:

import { useQuery } from "react-query";
...
export function App() {
  useQuery(
    ["character", { id: 1 }],  // query key
    getCharacter               // fetching function
  );
}

We pass a key into useQuery along with our fetching function.

In our example, the key is a tuple containing the resource name, "character", and the parameters to get a particular record, { id: 1 }.

The fetching function is getCharacter - we’ll look at this later.

useQuery has generic parameters for the type of the data that is being fetched and the error type if a failure occurs:

useQuery<Character, Error>(
  ["character", { id: 1 }],
  getCharacter
);

Error is the standard Error object.

The Character type is as follows:

type Character = {
  name: string;
};

useQuery returns useful state variables that can be destructured:

const { status, error, data } = useQuery<Character, Error>(
  ["character", { id: 1 }],
  getCharacter
);
  • status contains where we are in the fetching process ("idle" or "error" or "loading" or "success").
  • error contains an error object if the fetch errored.
  • data contains the data that has been successfully fetched.

Fetching function

The fetching function implementation is as follows:

type Params = {
  queryKey: [string, { id: number }];
};
async function getCharacter(params: Params) {
  const [, { id }] = params.queryKey;
  const response = await fetch(`https://swapi.dev/api/people/${id}/`);
  if (!response.ok) {
    throw new Error("Problem fetching data");
  }
  const character = await response.json();
  assertIsCharacter(character);

  return character;
}

The function takes in the query key. In this example, this is the resource name and parameters to get a specific record.

We do the request using fetch and throw an error if the response isn’t successful. React Query manages the error for us, setting the status state to "error" and error to the Error object raised.

The fetching function is expected to return the data from the request that we want to use in our component. We use a type assert function called assertIsCharacter to ensure the data is correctly typed:

function assertIsCharacter(character: any): asserts character is Character {
  if (!("name" in character)) {
    throw new Error("Not character");
  }
}

Finishing the component

Here’s the rest of the component implementation:

export 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;
}

We use the status state to display a loading indicator during the fetch. We also use status to display the error message if an error is raised.

When the data has been returned, we display its name property in an h3 element.

Benefits of React Query

Notice that we didn’t have to use useEffect and work around its async/await problem. We didn’t have to put the response data in state and handle the case when the component is unmounted in the middle of the request. React Query deals with this stuff for us and allows us to simplify our code. 😊

React query also automatically refetches data when an app regains focus, which is nice. This, of course, can be disabled, but I think this is nice default behavior.

The other thing we get with React query is that it will automatically retry a fetching function up to 3 times if an error occurs. Again this is nice default behavior, particularly if the app is running on a device with a flakey connection.

The code in this post is available in CodeSandbox at https://codesandbox.io/s/react-query-basic-uob4s?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