Getting Started with React Query and TypeScript
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.
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.If you to learn more about using TypeScript with React, you may find my course useful: