﻿# TanStack Query

`@lazarv/react-server` works with [TanStack Query](https://tanstack.com/query) (formerly React Query) to provide powerful data fetching with server-side prefetching and client-side hydration. You can prefetch queries in server components and seamlessly hydrate the data on the client, avoiding unnecessary re-fetches.

## Installation

Install TanStack Query in your project:

```sh
pnpm add @tanstack/react-query
```

## Setup

To use TanStack Query, you need to create a `QueryClient` and wrap your app in a `QueryClientProvider`. Since `QueryClientProvider` relies on React context, it must be a client component.

Create a query client factory that handles both server and browser environments:

```jsx filename="app/get-query-client.jsx"
import {
  defaultShouldDehydrateQuery,
  isServer,
  QueryClient,
} from "@tanstack/react-query";

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // With SSR, set some default staleTime above 0
        // to avoid refetching immediately on the client
        staleTime: 60 * 1000,
      },
      dehydrate: {
        // include pending queries in dehydration
        shouldDehydrateQuery: (query) =>
          defaultShouldDehydrateQuery(query) ||
          query.state.status === "pending",
      },
    },
  });
}

let browserQueryClient = undefined;

export function getQueryClient() {
  if (isServer) {
    // Server: always make a new query client
    return makeQueryClient();
  } else {
    // Browser: make a new query client if we don't already have one
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}
```

Create a client component that provides the `QueryClient` to the rest of your app:

```jsx filename="app/providers.jsx"
"use client";

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

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000,
      },
    },
  });
}

let browserQueryClient = undefined;

function getQueryClient() {
  if (isServer) {
    return makeQueryClient();
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}

export default function Providers({ children }) {
  const queryClient = getQueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}
```

Then wrap your app in the `Providers` component from your root layout:

```jsx filename="app/layout.jsx"
import Providers from "./providers";

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head />
      <body suppressHydrationWarning>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
```

## Server-side prefetching

The key advantage of using TanStack Query with `@lazarv/react-server` is the ability to prefetch data in server components and hydrate it on the client. This means data is available instantly without a loading state.

In a server component, use the query client to prefetch data and wrap your client components in a `HydrationBoundary`:

```jsx filename="app/page.jsx"
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";

import { getPosts } from "./get-posts";
import { getQueryClient } from "./get-query-client";
import Posts from "./posts";

export default function PostsPage() {
  const queryClient = getQueryClient();

  queryClient.prefetchQuery({
    queryKey: ["posts"],
    queryFn: getPosts,
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Posts />
    </HydrationBoundary>
  );
}
```

## Client components

Client components can use `useSuspenseQuery` (or `useQuery`) with the same query keys. When the data was prefetched on the server, it will be immediately available without a loading state:

```jsx filename="app/posts.jsx"
"use client";

import { useSuspenseQuery } from "@tanstack/react-query";
import { getPosts } from "./get-posts";

export default function Posts() {
  const { data } = useSuspenseQuery({
    queryKey: ["posts"],
    queryFn: getPosts,
  });

  return (
    <ul>
      {data.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}
```

## Isomorphic data fetching

To share data fetching logic between server and client, you can create isomorphic data fetchers that detect the runtime environment:

```js filename="app/get-posts.mjs"
export async function getPosts() {
  if (typeof document === "undefined") {
    // Server: import data directly
    const { default: posts } = await import("../data/posts.json");
    return posts;
  } else {
    // Client: fetch from API route
    const res = await fetch("/api/posts");
    return res.json();
  }
}
```

You can combine this with API routes using file-system routing:

```jsx filename="app/api/GET.posts.jsx"
import posts from "../../data/posts.json";

export default async function GET() {
  return new Response(JSON.stringify(posts), {
    status: 200,
    headers: {
      "Content-Type": "application/json",
    },
  });
}
```

## Nested hydration boundaries

You can nest `HydrationBoundary` components in separate server components to prefetch different sets of data. This is useful for composing multiple data dependencies:

```jsx filename="app/comments-server.jsx"
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";

import Comments from "./comments";
import { getComments } from "./get-comments";
import { getQueryClient } from "./get-query-client";

export default function CommentsServerComponent() {
  const queryClient = getQueryClient();

  queryClient.prefetchQuery({
    queryKey: ["comments"],
    queryFn: getComments,
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Comments />
    </HydrationBoundary>
  );
}
```

> Check out the [TanStack Query example](https://github.com/lazarv/react-server/tree/main/examples/react-query) to see a complete example of using TanStack Query with `@lazarv/react-server`.