How to manage server state with React Query

React Query is a library that simplifies the process of managing data fetching and caching in React applications. It provides a set of tools and utilities for fetching and updating data from APIs and other data sources and manages the state of data fetching and caching automatically. The library provides a comprehensive set of hooks and utilities that make it easier to work with data in React components.

In this post, I am going to talk about the key features of React Query. My purpose here is to provide you with a starting point so that you can start working with React Query as soon as possible.

If you have some experience developing React apps, you might have worked with libraries such as Redux for managing client states. In contrast, React Query is a library to manage server state. Therefore, before we talk about React Query, let's be familiar with the difference between client state and server state

Client state vs server state

let’s go through the difference between the client state and the server state. This is helpful in understanding what React query does and why there is such a need for another library to manage the server state.

Client StateServer State
LocationStored on the client (in the browser or device)Stored on a server or external data source
AccessibilityOnly accessible by the client that stored itAccessible by any client that has permission to access it
Data managementManaged by the client (e.g. using state management libraries like Redux)Managed by the server (e.g. using a database)
PersistenceMay or may not persist between sessionsTypically persists between sessions
Network requestsMay require network requests to fetch or update dataMay require network requests to access or update data
SecurityMaybe more secure, as it can be protected by authentication and encryptionMaybe more secure, as it can be protected by authentication and encryption
PerformanceMaybe less scalable than the server state, as it can be limited by the capacity of the client deviceCan be more scalable than the client state, as it can be managed by a dedicated server or data source with high capacity
ScalabilityMaybe less secure, as it is accessible to the client and may be subject to tampering or interceptionCan be faster to access and update than the server state, as it does not require network requests
ExamplesComponent state, Redux state, browser cookiesDatabase records, API responses, server session data

client state vs server state

Traditionally, React developers have used Redux or React Context API for client state management. When it comes to interacting with API data, they often use a combination of the useState hook and the useEffect hook to fetch API data and populate it in the client state.

React Query is a library that can replace the use of useState and useEffect hooks for fetching data from a remote data source, as it provides a set of hooks and utilities for managing and caching API data. It simplifies the process of fetching data from an API, handling the cache, and managing the loading and error states of API requests.

Four basic concepts in React Query

There are four basic concepts you must understand to work with React Query.

  1. Queries:

    1. Requests for data from a remote data source such as an API endpoint or database

    2. Managed by useQuery hook

  2. Mutations

    1. requests to add new data or modify existing data on the server

    2. Managed by useMutation hook

  3. Query caching: Query caching is a built-in feature of React Query that stores query results in memory

  4. Query invalidation: the process of marking a query as invalid or stale.

Data fetching with useQuery hook

On the official documentation, it says “A query is a declarative dependency on an asynchronous source of data that is tied to a unique key”

Now, what is that word salad !!!!

Here, the declarative dependency refers to the query you declare in the code ( using the useQuery hook ). This query is a request to the server to fetch data, asynchronously, from an API datapoint or database.

Now, let's see how we are going to do this with the useQuery hook

useQuery hook takes two required options( properties ):

  1. unique key

  2. a function that returns a promise

Syntax:

const query = useQuery( { queryKey: [ ‘key’ ],   queryFn: callback })

The querykey is the unique key. In the latest stable version of React Query, you need to use array notation, to specify this key.

The queryFn is a callback function that is executed by React Query when the useQuery hook is called. This function performs a specific task (fetching data) at a certain point in the execution of the useQuery hook.

Other than these two options, you can also have optional properties. I will cover some of the important ones in the following sections.

Example:

const getProducts = () => fetch( 'https://jsonplaceholder.typicode.com/users')
                            .then( res  => res.json() )


const query = useQuery( { queryKey: [‘users’],   queryFn: getTodos })

What does it returns

The useQuery hook returns an object. This object contains information about the state of the query. Some of the important properties and methods are:

properties

  1. data: The data returned from the query if it has been successfully fetched.

  2. error: Any error that occurred during the query, if there was one.

  3. isLoading: A boolean value indicating whether the query is currently loading or not.

  4. isError: A boolean value indicating whether the query resulted in an error or not.

Methods:

  • refetch: A function that allows you to manually trigger a refetch of the query data.

  • remove: A function that allows you to remove a specific query from the cache.

Therefore, you can apply JavaScri[pt destructuring assignment to access values of these properties

const { isLoading, isError, data, error } = useQuery( { queryKey: [‘todos’],   queryFn: getTodos });

You can find a complete reference of useQuery in the official documentation.

useQuery and refetching API data

By default, useQuery fetches data automatically from the API when the component is mounted for the first time. However, subsequent updates to the data are not fetched automatically. In other words, useQuery will not refetch data after an update in the API endpoint or server.

By default, useQuery options such as refetchOnMount, refetchOnReconnect, and refetchOnWindowFocus is set to true.

Take a look at the following table.

useQuery optionwhat it doesdefault value
refetchOnWindowFocusthe query will refetch on window focus if the data is stale.true
refetchOnMountrefetch on mount if the data is staletrue
refetchOnReconnectrefetch on reconnect if the data is staletrue

Assume that you keep your browser idle in the background. Someone make a request to update the API data/server. The data is updated. But, your UI is not updated because your browser is in the background. There is no trigger for useQuery to refetch the updated data from the API endpoint/server. In this situation, you can use refetchInterval option. You can run useQuery to refetch data at a frequency specified in milliseconds. This will enable you to refetch data even when the browser window is idle.

Create, update, and delete data with useMutation hook

Now, we will take a look at useMutation hook. You can use this hook to create, update, or delete data from the API endpoint or server.

The useMutation hook requires at least one option, and all other options are optional.

syntax:

const mutation =  useMutation({  mutationFn: mutationFunction })

Example:

const AddUser = useMutation({
      mutationFn: ( user ) => {

        return fetch('https://jsonplaceholder.typicode.com/users',
        {
          method:'post',
          headers: {
             "Content-Type": "application/json",          
          },
          body:JSON.stringify( user )
        }).then( res =>  res.json() )
      })

What does it returns

useMutation hook returns an object. This object contains several properties and methods that allow you to interact with the mutation and handle its results. Some of them are:

Properties

  • isLoading: A boolean indicating whether the mutation is currently in progress.`isLoading: A boolean indicating whether the mutation is currently in progress.

  • isSuccess: A boolean indicating whether the mutation has been completed successfully.

  • isError: A boolean indicating whether an error occurred during the mutation.

  • data: The data returned by the mutation, if any.

Methods

  • mutate: A function that you can call to execute the mutation. You can pass in an object with any necessary variables as the first argument.

  • reset: A function that resets the mutation to its initial state.

  • onSuccess: A function that allows you to define a callback to be called when the mutation completes successfully.

  • onError: A function that allows you to define a callback to be called when an error occurs during the mutation.

A complete reference to this hook can be found in the official documentation

Query Caching in React Query

Let’s assume that you use the useQuery hook to fetch data from some users from a remote server. It might take a while to receive that data. To speed things up, React Query will save this user data on the cache so that it can quickly be retrieved in subsequent requests. This helps reduce the time it takes to load the data.

As I mentioned before, the useQuery hook uses a unique key. The data returned from the server is cached under this key. For example, if you use [‘users’] it as the key, this key will be used to extract data from the cache for subsequent requests for the same API endpoint.

By default, useQuery will mark this cache data as stale. There are two options you need to be aware of when it comes to React query caching. That is staleTime and cacheTime.

staleTime: determine the time it takes query results to be considered stale. Time is specified in milliseconds.

Ex: staleTime: 5000 //data can remain stale for up to 5 seconds

This means data will be stale after 5 seconds

On the other hand, cacheTime option specifies how long the query results remain in the cache.

Ex: cacheTime: 60000// data will be in the cache for 1 minute

This means the query result will be in the cache for 1 minute and after that period data will be garbage collected.

const query = useQuery({
      queryKey: [‘users’],
      queryFn: getUsers,
      staleTime: 5000,
      cacheTime: 60000 
    })

Query Invalidation in React Query

As previously discussed, the data stored in the cache may become stale based on the staleTime option. However, situations may arise where it is necessary to override the specified staleTime and mark the data as invalid or stale. For instance, when sending a post request to the API, it is necessary to manually mark the data in the cache as invalid since the data at the API endpoint is the latest data. Consequently, the data in the cache is considered stale immediately after the POST request.

To address this, the React Query QueryClient object provides the invalidateQueries method. This method can be used to mark all or specific queries as stale. you can also mark a specific query as stale using its unique key.

import { useQueryClient } from '@tanstack/react-query'

//creating queryClient object using useQueryClient hook
const queryClient = useQueryClient()

// Invalidate every query in the cache
queryClient.invalidateQueries()

// Invalidate every query with a key that starts with 'users'
queryClient.invalidateQueries({ queryKey: ['users'] })

Wrapping up

In this post, I covered What React query is and why there is a need for it. React Query is a powerful library to manage server state. It utilizes React hooks such as useQuery to fetch data and useMutation to manipulate remote data from an API endpoint or server. React Query also has a smart caching mechanism to manage its in-memory cache. This makes sure that the cache synchronizes with the remote data source. Using React Query, you cut down your code and optimize the performance of React applications.

Resources

Download the full code

Note: In my demo project, I utilize a mock API I created with mockapi.io. However, I highly recommend creating your own API endpoint with mockapi.io. This is because it can be easier for you to do your experiment while you are learning.

Learn how to create a mock API on mockapi.io

React Query Installation

Queries in React Query

Mutations in React Query

Query Invalidation in React Query