DEV Community

Cover image for “Mastering React-Query: A Guide to Fetching and Managing State Like a Pro 😎”
Steven Christopher
Steven Christopher

Posted on

“Mastering React-Query: A Guide to Fetching and Managing State Like a Pro 😎”

React-Query is a powerful library that provides a great way to manage state in your React applications. If you’re still on the fence about whether or not to use it, here are a few reasons why you should consider using React-Query in your projects:

💪 It simplifies fetching and caching data.
💡 It provides a unified API for data fetching that works with any backend.
🚀 It supports optimistic updates, retries, and polling out of the box.
🕸 It integrates well with React and other popular libraries like Redux and MobX.
👀 It comes with a Devtools that helps you debug and optimize your queries
In this post, I’m going to go over how you can setup react-query in your projects and manage state like a pro 😎

Installation

For this example, we will be utilizing TypeScript with create-react-app. However, you can easily apply the principles outlined in this tutorial to any other React setup that you may be using.

npx create-react-app my-app --template typescript
Enter fullscreen mode Exit fullscreen mode

We need to install both the react-query library and its dev-tools.

npm install @tanstack/react-query
npm i @tanstack/react-query-devtools
Enter fullscreen mode Exit fullscreen mode

Let’s navigate to the index.tsx file located in the src directory and make the following modifications:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import "./index.css";

function Root() {
  const queryClient = new QueryClient();
  return (
    <React.StrictMode>
      <QueryClientProvider client={queryClient}>
        <App />
        <ReactQueryDevtools />
      </QueryClientProvider>
    </React.StrictMode>
  );
}

ReactDOM.render(<Root />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

In the snippet above, we are encapsulating our entire application with the QueryClientProvider component from React Query and providing it with a queryClient object, which we will discuss in more detail later on in this tutorial. Within the QueryClientProvider, we are also including ReactQueryDevtools, which will provide us with valuable insights into the workings of React Query.

👀 Upon starting the application and navigating to localhost:3000, you should see the following screenshot. The icon located at the bottom left corner of the screen indicates that we have successfully installed and set up the React Query Devtools, now we’re ready to get started ! 💪

Image description

Get Started

👉 In this example, we will be retrieving data from the Kanye REST API 🐐 and displaying a quote on the screen. Let’s get started. Navigate to the app.tsx file in the src directory and update with the following code.

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

// fetch quotes from api
const fetchQuotes = async () => {
  const response = await fetch("https://api.kanye.rest");
  const data = await response.json();
  return data.quote;
};

function App() {
  const { data: quote, isLoading, isError } = useQuery(["quotes"], fetchQuotes);

  if (isError) return <div>Something went wrong...</div>;

  if (isLoading) return <div>Loading...</div>;

  return (
    <div className="App">
      <button>Fetch Quotes</button>
      {quote}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

useQuery

const { data: quote, isLoading, isError } = useQuery(["quotes"], fetchQuotes);
Enter fullscreen mode Exit fullscreen mode

In this code snippet, we are importing the useQuery hook, which allows us to execute queries using React Query. The useQuery hook takes two arguments: a query key and a query function.

The query key is a unique identifier for the query and is used by React Query to keep track of the query’s state. The query function is a function that performs the actual data fetching operation and returns the data as a promise. This function can be either an asynchronous function or a regular function that returns a promise.

By using the useQuery hook, we can easily execute queries and manage the state of the data that we are fetching. The useQuery hook will automatically handle caching and re-fetching of the data as necessary.

queryFunction

// fetch quotes from api
const fetchQuotes = async () => {
  const response = await fetch("https://api.kanye.rest");
  const data = await response.json();
  return data.quote;
};
Enter fullscreen mode Exit fullscreen mode

Here, we are defining a queryFunction that sends a fetch request to the Kanye REST API and returns a promise. In this particular case, the promise will resolve with a quote retrieved from the API.

By defining the queryFunction separately from the useQuery hook, we can easily reuse it across multiple components if necessary. This can help to reduce code duplication and make our code more modular and maintainable.

Loading & Error Indicators

if (isError) return <div>Something went wrong...</div>;

if (isLoading) return <div>Loading...</div>;
Enter fullscreen mode Exit fullscreen mode

React Query provides two important states for handling loading and error statuses in data fetching: isLoading and isError.

isLoading is a boolean state that indicates whether the query is currently fetching data. When a query is initiated, isLoading is set to true. It remains true until the query is completed, either successfully or with an error. This state can be used to show a loading spinner or progress bar while the query is still running, so that the user knows that data is being fetched.

isError is a boolean state that indicates whether an error occurred while fetching data. When a query fails to retrieve data, isError is set to true. This state can be used to display an error message to the user or to handle the error in any way that makes sense for the specific use case.

Both of these states can be accessed through the useQuery hook. In addition to these states, React Query also provides other states and methods for more granular control over the query's state, such as isIdle, isFetching, and refetch.

Now the application should display a button and a random quote.

Image description

👍 Excellent! But what if we want to fetch a new quote and update the current one? How can we manage the fetched data using React state? 🤔 These are valid questions that I have asked myself as well. Let’s take a step-by-step approach to manage state with React Query, and you’ll find that it’s much simpler than you might have thought. 🚀

Handling State 🚀

Managing state with React Query is indeed simple, and we can dive deeper into the process of managing state in our React applications. Let’s begin by updating our App.tsx file with the following code:

import "./App.css";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

const fetchQuotes = async () => {
  const response = await fetch("https://api.kanye.rest");
  const data = await response.json();
  return data.quote;
};

function App() {
  const { data: quote, isLoading, isError } = useQuery(["quotes"], fetchQuotes);

  const queryClient = useQueryClient();

  const quoteMutation = useMutation(fetchQuotes, {
    onSuccess: (data) => {
      queryClient.setQueryData(["quotes"], data);
    },
    onError: (error) => {
      console.log(error);
    },
    onSettled: (data, error) => {
      console.log(data);
      console.log(error);
    },
  });

  const handleClick = () => {
    quoteMutation.mutate();
  };

  if (isError) return <div>Something went wrong...</div>;

  if (isLoading) return <div>Loading...</div>;

  return (
    <div className="App">
      <button onClick={handleClick}>Fetch Quotes</button>
      {quote}
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

useQueryClient

const queryClient = useQueryClient();
Enter fullscreen mode Exit fullscreen mode

The useQueryClient hook from React Query is used to access the QueryClient instance in any part of the application where the hook is used. The QueryClient instance is responsible for managing the cache of queries and mutations, as well as handling the background fetching and invalidation of data.

Note that it is important to use the useQueryClient hook when updating or invalidating queries in your application. Creating a new QueryClient instance will override the stored cache, which can result in unexpected behavior. Be sure to use the useQueryClient hook instead to ensure consistent and expected behavior.

useMutation 🧪

const quoteMutation = useMutation(fetchQuotes, {
    onSuccess: (data) => {
      queryClient.setQueryData(["quotes"], data);
    },
    onError: (error) => {
      console.log(error);
    },
    onSettled: (data, error) => {
      console.log(data);
      console.log(error);
    },
  });
Enter fullscreen mode Exit fullscreen mode

The useMutation hook from React Query is a utility function that allows you to easily send and manage mutations, or data-changing operations, to your backend API. It is similar to the useQuery hook, but instead of fetching data, it is used for creating, updating or deleting data.

In this code snippet, we are defining three callback methods: onSuccess, onError, and onSettled. Each of these methods serves a specific purpose:

  • onSuccess: This method runs when the mutation function completes successfully.
  • onError: This method runs if the mutation function throws an error.
  • onSettled: This method runs regardless of the outcome of the mutation function, similar to a finally clause in a try-catch statement.

onSuccess ✨

onSuccess: (data) => {
  queryClient.setQueryData(["quotes"], data);
},
Enter fullscreen mode Exit fullscreen mode

In this code snippet, we are using the queryClient instance to call the setQueryData method, which takes in a queryKey and the updated queryData. The onSuccess callback method provides us with the return value of the mutationFunction that we passed, and we can use that data to update our quote.

mutate

const handleClick = () => {
  quoteMutation.mutate();
};
Enter fullscreen mode Exit fullscreen mode

Finally, we are calling the mutate method on the quoteMutation that we created. This will trigger the mutate method and run all of the code we defined above. On success, it should update our quote and re-render the component!

Image description

DevTools

🛠️👀🔍 The React-Query Devtools is a tool that allows you to inspect and debug your React-Query state in real-time. It provides a visual representation of your query and mutation state, making it easier to see what’s happening behind the scenes. With this tool, you can quickly identify any issues with your queries and mutations and make necessary changes.

Image description

To access information about your queries, such as their status and the data they contain, click on the react-query icon located at the bottom left corner of your running application. This should prompt a screen to appear over the application, allowing you to view query details.

Final Application Code

index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import "./index.css";

function Root() {
  const queryClient = new QueryClient();
  return (
    <React.StrictMode>
      <QueryClientProvider client={queryClient}>
        <App />
        <ReactQueryDevtools />
      </QueryClientProvider>
    </React.StrictMode>
  );
}

ReactDOM.render(<Root />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

app.tsx

import "./App.css";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

const fetchQuotes = async () => {
  const response = await fetch("https://api.kanye.rest");
  const data = await response.json();
  return data.quote;
};

function App() {
  const { data: quote, isLoading, isError } = useQuery(["quotes"], fetchQuotes);

  const queryClient = useQueryClient();

  const quoteMutation = useMutation(fetchQuotes, {
    onSuccess: (data) => {
      queryClient.setQueryData(["quotes"], data);
    },
    onError: (error) => {
      console.log(error);
    },
    onSettled: (data, error) => {
      console.log(data);
      console.log(error);
    },
  });

  const handleClick = () => {
    quoteMutation.mutate();
  };

  if (isError) return <div>Something went wrong...</div>;

  if (isLoading) return <div>Loading...</div>;

  return (
    <div style={{ paddingTop: "5rem" }} className="App">
      <button onClick={handleClick}>Fetch Quotes</button>
      {quote}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Success! 🎉

🎉 Congratulations! 🎉 You can now fetch and manage state using react-query like a pro. I hope this article was helpful to you. 💻📚🤓

I would have loved to read something like this when I was first learning react-query, which is why I put it together. If you have any further questions or feedback, please drop them below. 💬👇

I would love to hear your thoughts on this tutorial!

Top comments (0)