React-Query is a library that allows you to make requests and handle response metadata. To make a query, define a unique key and an asynchronous function to resolve data, as params of useQuery.

Basic implementation:

import { useQuery } from react-query

function App() {
    const info = useQuery(todos, fetchTodoList)
}

In this case, todos is the unique key, and fetchTodoList is the async function.

Complex implementation:

function Todos() {
  const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList);

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

  if (isError) {
    return <span>Error: {error.message}</span>;
  }
  // also status === 'success', but "else" logic works, too
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

If you prefer to avoid booleans to handle the different states, a status variable is provided by the library:

function Todos() {
    const { status, data, error } = useQuery('todos', fetchTodoList)

    if (status === loading) {
        return <span>Loading...</span>
    }

    if (status === error) {
        return <span>Error: {error.message}</span>
    }
    // also status === 'success', but "else" logic works, too
    return (
        <ul>
            {data.map(todo => (
                <li key={todo.id}>{todo.title}</li>
            ))}
        </ul>
    )
}

As we can see, it’s very simple to implement and we can handle which element renders depending on the status.

React-query vs custom hook

Suppose we have a custom hook called useFetch:

import { useState, useEffect } from "react";

const useFetch = (url, options) => {
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);
      try {
        const res = await fetch(url, options);
        const json = await res.json();
        setResponse(json);
        setIsLoading(false);
      } catch (error) {
        setError(error);
      }
    };

    fetchData();
  }, []);

  return { response, error, isLoading };
};

This is already quite complex, and we did not even start to account for caching, invalidation and other features. The benefits of react-query library are:

The list of benefits is extensive, and it can be extended further if we decide to explore the library more. Let’s go one step further to usages.

Usages

If we explore the documentation of the library, we can see a lot of parameters that can be set up to do many things. From making parallel, paginated, dependent, load-more and infinite scroll queries, to caching them. But I want to highlight something very important here, query results (via useQuery and similar hooks) will become “stale” immediately after they are resolved and will be refetched automatically in the background when they are rendered or used again. To change this, there are 2 ways to do it. First one, you can alter the default staleTime for queries to something other than 0 milliseconds. And the other way is using the third param of useQuery to set the manual in true. Besides that, we can use refetch to call when you want and update data. It could be depending on a variable or maybe in a callback.

Example 1:

const {
  status: randomStatus,
  data: randomData,
  refetch: refetchRandom,
} = useQuery("randomCharacter", getCharacterRandom, { manual: true });

Example 2:

const { status: allStatus, data: allData, refetch: refetchSearch } = useQuery(
  "characters",
  () => getCharacters(characterSearch),
  {
    manual: true,
  }
);

Other importants things are:

If I have to implement multiple queries, I can rename the params that useQuery returns. On example 1, function doesn’t have parameters, but as we see on example 2, the async function needs one. If we only do this: getCharacters(characterSearch), we are executing the function. That’s why we resolved implementing it with an arrow function.

There’s many ways to optimize and options to set up react-query depending what you want to do. Explore this fantastic and useful library.

Should we replace the traditional custom hook to handle the response?

I think we can replace the traditional custom hook to make requests without using useState and useEffect, with many lines of code to return data in a success response, isLoading while is requesting, or error in that case. We could have a Hooks folder, in which we could have many files depending on the topic or setting of the queries. I also believe that react-query gives you the freedom to implement your queries in a way that fits your application architecture.