Whatsapp

Explore and Understand React Query: Simplify Data Fetching and State Management

author
Pramesh Jain
~ 12 min read
Explore and Understand About React Query

Summary: React Query is a powerful library that simplifies data fetching and state management in React apps. With React Query, experts can manage asynchronous data without the need for complex state management libraries like Redux. Let’s dive into the blog to explore and understand react queries.

What is React Query?

React Query is a library to manage asynchronous data in React apps. It has a simple and flexible API to fetch, cache, and update data in real time. React Query is designed to work well with modern React features like hooks, context, and suspense.

Why Use React Query?

React Query provides several benefits for developers:

  • Simplified data fetching: React Query makes fetching data from APIs and other data sources easy. It automatically handles caching, retries, and other complexities, so developers can focus on building their applications.
  • Real-time updates: React Query provides real-time updates for data that changes often. It uses web sockets and other methods to keep data up-to-date in real time.
  • Improved performance: React Query uses caching and other optimization techniques. It minimizes unnecessary re-renders and reduces the load on servers.
  • Simplified state management: React Query simplifies state management by automatic handling of data fetching and caching. Experts can avoid complex state management libraries like Redux and MobX.

Getting Started with React Query:

To get started with the latest version of React Query, run the below code

npm i @tanstack/react-query

or

yarn add @tanstack/react-query

Once installed, you can use React Query in your React components.

Note: If you move to the new version of React Query (i.e., Tanstack Query), and if you were using ‘react-query/devtool,’ then replace it to install Tanstack Query. Otherwise, it will throw an error.

For that, run the below code:

npm i @tanstack/react-query-devtools

State Management

State management is a crucial task for a web app. Majorly there are two kinds of states: client state and server state

Let’s know about them in detail.

Client State VS Server State

Client State VS Server State

Client State

  • Ephemeral: It vanishes when the browser is closed and is ephemeral.
  • Synchronous: It is always accessible.
  • Client-owned: It remains restricted to the original browser. 

The client state is only kept in the browser. It is when using third-party state management libraries like Redux or MobX, or hooks like useState or useReducer. It helps hold UI-related data before is delivered to the server, such as open menus, dark mode choices, or form input.

Server State

  • Remote storing: The client is not in charge of how or what is stored.
  • Asynchronous: It takes some time for the client to receive data from the server.
  • Owned by many users: The data could be altered by multiple users.

When a website is accessed, the server state is saved on the server and persistently communicated to the browser. To retain user profiles, comments, and analytics info between browser sessions, it is used to save the details.

It’s crucial to analyze how the server state is saved on the client, as many clients can connect to the same server and alter its state. Data from the server could easily become out-of-date because the server state is kept remotely and is subject to change by any user. As a result, we must handle it carefully.

Comparison: React Query & React’s Built-In useEffect

React Query

Specialized data fetching tools like SWR, React Query, and Apollo Client can be used to manage server states in React apps. Aside from offering built-in support for server-side rendering and real-time updates, these libraries also make data fetching easier and manage server state with caching, pagination, and automatic handling of loading and error situations.

React’s Built-In useEffect

As an alternative, useEffect the hook, a feature included in React, can be utilized for finer-grained control over data fetching and state management. You can also implement side effects in your components using the useEffect function in react js. Data retrieval, direct DOM updates, and timers are a few examples of side effects from the react useEffect library.

//using useEffect. manually handle isLoading, isError, isFetching, and refetch API.
export const Random = () => {
 const [key, forceUpdate] = useReducer(x => x + 1, 0);
 const [num, setNum] = useState();
 const [loading, setLoading] = useState(false);
 const [error, setError] = useState("");
 useEffect(() => {
   setLoading(true);
   fetch("<https://www.random.org/integers/?num=1&min=1&max=9&col=1&base=1&format=plain>")
     .then((response) => {
        if (response.status !== 200) {
          return {
           error: `Something went wrong. Try again.`
          };
        }
       return response.text();
     })
     .then((random) => {
       setLoading(false);
       if (isNaN(Number(random))) {
         const errorResponse = JSON.parse(random);
         setError(errorResponse.error);
       } else {
         setNum(random);
       }
     });
 }, [key]);
 if (error) return <p>{error}</p>;
 return (
   <button onClick={forceUpdate}>
     Random number: {loading? "...": num}
   </button>
 );
};

How React Query Solves Server State?

//use this automatically to handle refetching, isloading state, error, and refetch.
import { useQuery } from "react-query";

const fetchNumber = () => {
  return fetch("https://www.random.org/integers/?num=1&min=1&max=100&col=1&base=10&format=plain&rnd=new")
    .then((response) => {
      if (response.status !== 200) {
        throw new Error(`Something went wrong. Try again.`);
      }
    
      return response.text();
    })
}

export const Random = () => {
  const query = useQuery(
    ["random"],
    fetchNumber,
  );

  if (query.isError) return <p>{query.error.message}</p>;

  return (
    <button onClick={() => query.refetch()}>
      Random number: {
        query.isLoading || query.isFetching ? "..." :
         query.data
      }
    </button>
  );
};

Now, let’s dive into React Query.

Queries

The useQuery hook is used to fetch data from an API using React Query. The hook takes two arguments:- 

  • The first is a unique key for the query
  • The second is a function that returns a Promise with the data

The key is used by React Query to cache the data and provide real-time updates. The function should return a Promise that resolves to the data you want to fetch.

Here is an example to use useQuery to fetch a list of to-dos:

import { useQuery } from '@tanstack/react-query';

function Todos() {
  const { isLoading, isError, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
  })

  if (isLoading) {
    return <span>Loading...</span>
  }

  if (isError) {
    return <span>Error: {error.message}</span>
  }

  // We can assume by this point that `isSuccess === true`
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

In this example, useQuery is used to fetch a list of todos from the Server. The queryKey ‘todos’ is used to cache the data. And the queryFn is a function given to useQuery to return the data as a Promise.

The isLoading flag shows a loading indicator while the data is being fetched. If there is an error, then the object is returned and displayed. If the data is successfully fetched, it is displayed as a list of todos.

Mutations

In addition to handling data fetching and caching, React Query also provides support for mutations. Mutations allow you to modify server data and update the client’s cache.

To use mutations, you can use the useMutation hook:

import { useMutation } from 'react-query';

function AddTodo() {
  const [newTodo, setNewTodo] = useState('');

  const [addTodo, { isLoading }] = useMutation((text) =>
    fetch('<https://jsonplaceholder.typicode.com/todos>', {
      method: 'POST',
      headers: { 'Content-Type': 'application/JSON },
      body: JSON.stringify({ title: text, completed: false })
    }).then(res => res.json())
  );

  const handleSubmit = (e) => {
    e.preventDefault();
    addTodo(newTodo);
    setNewTodo('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={newTodo}
        onChange={(e) => setNewTodo(e.target.value)}
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Adding...': 'Add Todo'}
      </button>
    </form>
  );
}

In this example, the useMutation hook is used to add a new to-do item to the JSONPlaceholder API. The isLoading flag is used to disable the submit button while the mutation progresses.

Configuring Mutations in React Query

React Query also provides many options for config. mutations, such as error handling, optimistic updates, invalidation, prefetching, etc.

  • React Query provides automatic query invalidation and prefetching.
  • Query invalidation can be triggered manually using the invalidateQueries function or automatically by React Query based on certain conditions.
  • When a query is invalidated, React Query marks it as stale and schedules a prefetch in the background.
  • Queries can also be fetched manually using the prefetch function.
  • React Query has many options for configuring query invalidation and prefetching, such as setting a minimum interval between prefetches and disabling automatic prefetching.
import {
useQuery,
useQueryClient,
useMutation,
} from '@tanstack/react-query'

function Example() {
const queryClient = useQueryClient();

const { status, data, error } = useQuery(['todos'], async () => {
const res = await axios.get('/api/data');
return res. data;
});

const addMutation = useMutation((value) => fetch(`/api/data?add=${value}`), {
onSuccess: () => queryClient.invalidateQueries(['todos']),
});

}

Build an App Using React Query With Us!

Consult us

Query Caching

One of the most powerful features of React Query is its built-in query caching. When you use useQuery to fetch data, React Query automatically caches the data and provides real-time updates. This means that if you fetch the same data multiple times, React Query will only make one API call and return the cached data.

Here’s an example of using query caching in React Query:

import { useQuery } from '@tanstack/react-query';

function Todos() {
  const { isLoading, isError, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
    staleTime: 30000,
  })

  if (isLoading) {
    return <span>Loading...</span>
  }

  if (isError) {
    return <span>Error: {error.message}</span>
  }

  // We can assume by this point that `isSuccess === true`
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

In this example, useQuery is used to fetch a list of todos from an API. The queryKey is set to [‘todos’], which tells React Query to cache the data using this key. The queryFn is a function that returns the data as a Promise. 

The staleTime option is set to 30000 milliseconds (30 seconds). It means that if this query hasn’t been prefetched within 30 seconds, React Query will mark it as stale and schedule a background fetch.

React Query has many options to config. query caching, like cacheTime, refetchInterval, refetchOnMount, and more. These options allow React developers to fine-tune the caching behavior of the apps and optimize performance.

Automatic Data Fetching

One of the most powerful features of React Query is its ability to fetch data based on certain conditions automatically. For example, React Query can automatically fetch data when it becomes stale. When the user navigates to a new screen or the app returns online after being offline.

To enable automatic data fetching in React Query, you can use the useQuery hook with the refetchInterval option:

import { useQuery } from "@tanstack/react-query";

function Todos() {
  const { isLoading, isError, data, error } = useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodoList,
    refetchInterval: 1000 * 60 // Refetch every minute
  });

  if (isLoading) {
    return <span>Loading...</span>;
  }

  if (isError) {
    return <span>Error: {error.message}</span>;
  }

  // We can assume by this point that `isSuccess === true`
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

In this example, useQuery fetches a list of todos from an API. The refetchInterval option is set to 1000 * 60. It means that the query will be automatically refetched every minute. It also ensures that the data is always up-to-date, avoids manual interventions.

React Query provides many options for automatic data fetching, such as refetchOnMount, refetchOnWindowFocus, and more. These options allow you to fine-tune your app’s automatic data fetching behavior and ensure that your data is always up-to-date.

Optimistic Updates

Optimistic updates, or optimistic UI, improve user interfaces by providing instant feedback during client-server interactions.

When a user modifies server data, the client immediately updates the UI as if the action succeeded without waiting for server confirmation. This enhances responsiveness and perceived speed. The update remains if the server confirms success. Otherwise, the client handles rollback or error recovery. 

Optimistic updates are beneficial when server response time is slow. It also depends on user responsiveness, giving users a sense of quick processing even with delayed server responses.

Here’s a code for it:

const queryClient = useQueryClient()
useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {

// Cancel any outgoing refetches, so they don't overwrite our optimistic update
    await queryClient.cancelQueries({ queryKey: ['todos'] })
    const previousTodos = queryClient.getQueryData(['todos'])

// Optimistically update to the new value
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
    return { previousTodos }
  },

//Use the context returned from onMutate to roll back on mutation fails
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previousTodos)
  },

// Always refetch after error or success:
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})

Overall, optimistic updates enhance the user experience by reducing perceived latency and providing a more responsive interface, but they require careful implementation and consideration of potential error scenarios to ensure data consistency between the client and the server.

Discuss Your App Idea With Us!

• We have 12+ years of experience
• 500+ projects are deployed by us in 25+ industries
• Get a free quote from our experts

Let’s Talk

Conclusion

React Query is a powerful library that simplifies data fetching and state management in React apps. It provides a simple and flexible API for fetching, caching, and updating data in real time. With React Query, experts can build high-performance React apps without the need for complex state management libraries.

We hope after reading this blog, you will be able to know the basics of React Query, the syntax may defer if you are using the old version or the latest version of React Query.

For further queries, you can consult us for free with your app idea. We have been in the industry for 12+ years & recognized as a top software developer in India and a top web development company. Get a free quote from us!

React Query

Hire Remote Developers

Scale up your project with our dedicated team of developers & deploy your project in time.

Hire Dedicated Developers
Subscribe to Our Newsletter!

Stay Updated to the Technology Trends for Every Industry Niche.