DEV Community

Cover image for How does React Query's useQuery work?
Nathaniel Arfin
Nathaniel Arfin

Posted on

How does React Query's useQuery work?

I'm a big fan of React Query. Most often, I combine it with an express backend to power my APIs. I love React Query because it handles cacheing, and makes background updates and side-effects really easy.

If you want to see how I implement it in a more fulsome project, check out my FERN Walkthrough.

Something I love to do when I'm learning a new technology is take a deep dive into the "how". A peek behind the curtain into useQuery is incredibly enlightening, and helps to inform the ways you can use in your projects.

What is useQuery?

From the docs: 'A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server.'

Or, in regular people English, it's an assigned constant to a react hook, that you provide a consistent name, and a function that returns a Promise.

That's still not exactly clear. Let's take a look at what we're working with. What is useQuery?

At it's very core, useQuery is a custom hook.

Breaking down the Custom Hook

More specifically, this custom hook:

function useQuery(queryKey, queryFn, options = {}) {
  const { enabled = true, retry = 3, refetchInterval } = options; 
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('idle');
  const [retryCount, setRetryCount] = useState(0);

  useEffect(() => {
    let intervalId;

    const fetchData = async () => {
      setStatus('loading');

      try {
        const result = await queryFn();
        setData(result);
        setError(null);
        setStatus('success');
      } catch (err) {
        setError(err);
        setStatus('error');

        if (retryCount < retry) {
          setRetryCount((prevCount) => prevCount + 1);
        }
      }
    };

    if (enabled) {
      fetchData();
      if (refetchInterval) {
        intervalId = setInterval(fetchData, refetchInterval);
      }
    }

    return () => {
      if (intervalId) {
        clearInterval(intervalId);
      }
    };
  }, [queryKey, queryFn, enabled, retry, refetchInterval, retryCount]);

  return { data, error, status };
}
Enter fullscreen mode Exit fullscreen mode

So let's break this down. The useQuery hook takes 3 arguments:

  • 'queryKey' - this is used to cache data, and can be used to trigger refreshes as required.
  • 'queryFn' - A function which returns data and throws on error.
  • 'options' object such as a number of retries, a refetchInterval, and the ability to programatically disable the hook.

On run, the useQuery hook does a couple things to handle the fetching process. First, it sets up the state to track data, error, status, and retry count. Next, it uses the useEffect hook to perform the passed in 'queryFn', fetch data, and update the state accordingly. While it does this, the hook handles errors, retries, and refetch intervals, making sure the component has the most up-to-date data available. Additionally, the hook can be programmatically enabled or disabled through the options object, giving you more control over its behavior.

Wrapping up

By making us of the useQuery custom hook in your projects, you can easily fetch and manage data from your API endpoints while leveraging built-in caching and background updates. It met your code more readable and performant, and leads to better user experiences. Now that we have better understanding of how the useQuery hook works, how do you plan to implement it in your next project?

Top comments (0)