Effortlessly Manage Data Fetching in Next.js with useQuery

Effortlessly Manage Data Fetching in Next.js with useQuery

Introduction

We've all been there—stuck in the weeds of data fetching, managing state, and handling errors, all while trying to build a sleek Next.js app. It can feel like a never-ending battle. But what if I told you there's a way to make this process almost effortless? Enter useQuery from React Query. This hook takes the headache out of data fetching, so you can focus on what you love: crafting amazing user experiences. In this blog post, I'll show you how useQuery can transform your Next.js projects, making your data fetching smooth, efficient, and virtually hassle-free.

What is useQuery?

useQuery is a hook from the React Query library designed to handle server state in your applications. It provides an elegant solution for fetching data, caching it, and keeping your UI in sync with the server state. By using useQuery, you can focus more on building your application and less on managing the intricacies of data fetching.

Before useQuery

Let's start by looking at a typical approach to data fetching in a Next.js component, without using useQuery.

import { useEffect, useState } from 'react';

const AdviceGenerator = () => {
  const [advice, setAdvice] = useState('');
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchAdvice = async () => {
    setIsLoading(true);
    try {
      const response = await fetch('https://api.adviceslip.com/advice');
      const data = await response.json();
      setAdvice(data.slip.advice);
    } catch (err) {
      setError(err);
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    fetchAdvice();
  }, []);

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

  return (
    <div>
      <h1>{advice}</h1>
      <button onClick={fetchAdvice}>Get New Advice</button>
    </div>
  );
}

export default AdviceGenerator;

Here, we have to manage the loading state, error state, and data fetching manually using useState and useEffect. It works, but it can get messy and repetitive.

After useQuery

Now, let's see how useQuery can simplify this process.

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

const fetchAdvice = async () => {
  const response = await fetch('https://api.adviceslip.com/advice');
  const data = await response.json();
  return data.slip.advice;
};

const AdviceGenerator = () => {
  const queryClient = useQueryClient();
  const { data: advice, error, isLoading, refetch } = useQuery('advice', fetchAdvice, {
    refetchOnWindowFocus: false,
  });

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

  return (
    <div>
      <h1>{advice}</h1>
      <button onClick={() => refetch()}>Get New Advice</button>
    </div>
  );
}

export default AdviceGenerator;

With useQuery, we delegate the loading and error handling to React Query, significantly simplifying our code.

Installation

Getting started with useQuery in a Next.js project is straightforward. First, you need to install the React Query library:

npm install @tanstack/react-query

or

yarn add @tanstack/react-query

Basic Usage

Let's look at a simple example where we fetch data from an API and display it in a Next.js component.

Step 1: Setting Up the Query Client

First, you'll need to set up the React Query client in your _app.js or _app.tsx file.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

const MyApp = ({ Component, pageProps }) => (
  <QueryClientProvider client={queryClient}>
    <Component {...pageProps} />
  </QueryClientProvider>
);

export default MyApp;

Step 2: Using useQuery in a Component

Now, let's create a component that uses useQuery to fetch data.

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

const fetchAdvice = async () => {
  const response = await fetch('https://api.adviceslip.com/advice');
  const data = await response.json();
  return data.slip.advice;
};

const AdviceGenerator = () => {
  const queryClient = useQueryClient();
  const { data: advice, error, isLoading, refetch } = useQuery('advice', fetchAdvice, {
    refetchOnWindowFocus: false,
  });

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

  return (
    <div>
      <h1>{advice}</h1>
      <button onClick={() => refetch()}>Get New Advice</button>
    </div>
  );
}

export default AdviceGenerator;

Smart Caching

One of the standout features of useQuery is its smart caching mechanism. When you fetch data with useQuery, it automatically caches the results. This means that if you request the same data again, useQuery can serve it from the cache instead of making a new network request. This not only improves performance but also provides a smoother user experience. The cache is intelligently invalidated and updated based on your configuration, ensuring that your app always has fresh and accurate data.

Advanced Usage

React Query offers a lot of flexibility and advanced features to handle more complex use cases.

Fetching Advice by ID with Dependent Queries

Query keys can be dynamic, allowing you to fetch data based on different parameters. Additionally, you can use the enabled option to create dependent queries that only run when certain conditions are met.

const fetchAdviceById = async (id) => {
  const response = await fetch(`https://api.adviceslip.com/advice/${id}`);
  const data = await response.json();
  return data.slip.advice;
};

const AdviceGeneratorById = ({ adviceId }) => {
  const { data: advice, error, isLoading, refetch } = useQuery(
    ['advice', adviceId], 
    () => fetchAdviceById(adviceId),
    {
      enabled: !!adviceId, // The query will only run if adviceId is truthy
    }
  );

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

  return (
    <div>
      <h1>{advice}</h1>
      <button onClick={() => refetch()}>Get New Advice</button>
    </div>
  );
}

export default AdviceGeneratorById;

Conclusion

As you can see, useQuery from React Query provides a powerful and flexible solution for managing server state in your Next.js applications. By leveraging its capabilities, you can simplify your data fetching logic, benefit from smart caching, and handle more complex use cases with ease. Whether you're building a simple advice generator or a more complex app, useQuery can help you streamline your development process and enhance your app's performance. Give it a try in your next project and experience the difference!

Additional Resources