# 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`.

```jsx
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.

```jsx
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:

```bash
npm install @tanstack/react-query
```

or

```bash
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.

```jsx
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.

```jsx
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.

```jsx
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

* [useQuery | TanStack Query React Docs](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery)
