All Articles

Things I learned while using React Query - Part 2

☕️ 4 min read

This blog post is the second in a series:

  1. Things I learned while using React Query - Part 1
  2. Things I learned while using React Query - Part 2 (this post)

Disable some of the defaults while in development

React Query comes with some aggressive defaults that are useful in production but not that much while developing.

For example, by default, a refetch happens in the background on window focus to keep the user as up to date as possible with the server. In development you really don’t need to sync with the server so often.

The same goes for the automatic retry behaviour when the query fails. Having no retry for queries that fail is perfectly acceptable in development and it will improve your development speed.

I recommend that you disable these two defaults at the level of the query client. The advantage of doing it here is that you do it in only one place and you don’t need to worry about the other queries in your app.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: process.env.NODE_ENV === 'production',
      refetchOnWindowFocus: process.env.NODE_ENV === 'production',
    },
  },
})

Configure staleTime based on your needs

If you know that a certain query doesn’t change often, probably you should change the staleTime from the default value of zero, to a value that best fits your needs for that specific query.

Use the enabled option to create dependent queries or to disable/enable a query

I’ve seen many people having a hard time running a query conditionally. Since hooks don’t work with if statements, React Query provides the enabled configuration option exactly for this. You can disable/enable a specific query by providing true or false to the enabled option.

Another useful features that comes with the enabled option is the ability to create dependent queries. You fetch data in one query and the second query runs only after the first one successfully completes.

Treat query keys like a dependency array, useEffect style

React Query does the cache management based on your query keys which means that your query keys uniquely describe a piece of data in your application. These query keys can be simple string values, complex nested objects or array of strings and complex nested objects.

Many of your fetching functions will have dynamic route parameters or query parameters. Think of the situations when you want to fetch a resource based on its id or when you are doing server side pagination or filtering.

Knowing this, it’s a good idea when designing your query keys, to treat them like a dependency array, just like you do with your useEffect hooks. The rule of thumb is to add to the query key any variable that your fetch function depends on.

The benefit of doing this is that React Query will automatically trigger a refetch whenever the query key changes and the synchronisation with the server just happens.

Create custom hooks

A good practice is to wrap your useQuery hook calls in your own custom hook. Functionality wise there is no added benefit, but there are a few developer benefits.

  • First, we separate our data fetching from the UI
  • Second, we can be sure that we are NOT using different query keys for the same data
  • Lastly, if we need to tweak some settings for a specific query, or add some data transformation, we do that in only one place

Don’t be afraid to use your hook in every component you need

If you need the same piece of data across your application in multiple components, don’t be afraid to use your custom hook (or the useQuery hook with the same query key) in multiple components.

React Query automatically de-duplicates queries based on the query key, therefore you can be sure there will not be more than one request per query key.

Use a default query function

To make things even more simpler, you can share the same fetch functionality for queries throughout your application by creating a default query function.

As I told you before, many of your fetching functions will have dynamic route parameters or query parameters. This means that we can create a default query function that we can use for all of our queries.

There are two steps we need to do: create the general fetch function and specify to React Query that we are going to use a default query function and which is the function we want to use.

First, let’s create the general function

function createQueryFn(baseUrl: string): QueryFunction {
  return async ({queryKey}) => {
    const path =
      typeof queryKey === 'string' ? queryKey : queryKey[0] + qs(queryKey[1])
    const res = await fetch(baseUrl + path)

    if (!res.ok) throw new Error(await res.json())

    return res.json()
  }
}

And second, we need to tell React Query about this function

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      queryFn: createQueryFn('https://api_base_url'),
    },
  },
})

Once this is done, you can use the useQuery hook in the following ways:

// query with simple query key as string
export function useMovies() {
  // a GET request will be fired to https://api_base_url/api/movies
  return useQuery('/api/movies')
}
// OR
// query with complex query key as object
export function useMovies({page, pageSize, searchTerm}) {
  // a GET request will be fired to
  // https://api_base_url/api/movies?page=0&pageSize=10&searchTerm=matrix
  return useQuery('/api/movies', {page, pageSize, searchTerm})
}