# Quick start

> Set up a QueryClient, define a query factory, start it in a TanStack Start loader and read it with useQuery and use().

This walks through the three core concepts of raf-query on [TanStack Start](https://tanstack.com/start):

- **Query factories**, which describe a query once
- **Starting queries** in a loader, which hands components a handle
- **Reading queries** with `useQuery` and `use()`

For the Next.js App Router, the same steps apply with a server component in place of the loader: see [React Server Components](https://raf-query.katt.dev/docs/react-server-components.md). For React Router Framework Mode, see [React Router](https://raf-query.katt.dev/docs/react-router.md).

## 1. Create the client

Create one `QueryClient` per router. On the server, `getRouter` runs once per request, so each request gets its own cache; in the browser it runs once per tab.

```tsx title="src/router.tsx"
import { QueryClient, QueryClientProvider } from "@raf-query/react";
import { QueryDevtools } from "@raf-query/react/devtools";
import { setupRouterIntegration } from "@raf-query/react/tanstack-router";
import { createRouter } from "@tanstack/react-router";

import { routeTree } from "./routeTree.gen.ts";

export function getRouter() {
	const queryClient = new QueryClient();
	const router = createRouter({
		context: { queryClient },
		defaultPreload: "intent",
		// The query cache decides what's fresh; loaders always run and dedupe there.
		defaultPreloadStaleTime: 0,
		routeTree,
		scrollRestoration: true,
		Wrap: (props) => (
			<QueryClientProvider client={queryClient}>
				{props.children}
				{import.meta.env.DEV ? <QueryDevtools /> : null}
			</QueryClientProvider>
		),
	});
	setupRouterIntegration({ queryClient, router });
	return router;
}
```

`setupRouterIntegration` streams the server's cache to the browser while the page renders, so data loaded on the server is never fetched twice. In development, [DevTools](https://raf-query.katt.dev/docs/devtools.md) show what's in the cache. Give the root route the client's type:

```tsx title="src/routes/__root.tsx"
import type { QueryClient } from "@raf-query/react";
import { createRootRouteWithContext } from "@tanstack/react-router";

export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
	{},
);
```

## 2. Define a query

A query is a **factory**: a function of at most one argument that returns `queryOptions(...)`. The argument is the query's input. Loaders and components call the same factory, so the data's type flows end to end.

```ts title="src/queries.ts"
import { infiniteQueryOptions, queryOptions } from "@raf-query/react";

import { getTodo, listTodos } from "./api.ts";

export function todosQuery() {
	return infiniteQueryOptions({
		queryKey: ["todos"],
		queryFn: async () => ({
			items: await listTodos(),
			nextCursor: null,
		}),
		initialPageParam: 0,
		getNextPageParam: (lastPage) => lastPage.nextCursor,
	});
}

export function todoQuery(input: { id: number }) {
	return queryOptions({
		queryKey: ["todos", input.id],
		queryFn: () => getTodo(input.id),
		staleTime: 30_000,
	});
}
```

## 3. Start it in the loader

The loader starts the query and returns its **handle**, a small `{ key, input }` object:

```tsx title="src/routes/todos.tsx"
import { type QueryHandle, useQuery } from "@raf-query/react";
import { createFileRoute } from "@tanstack/react-router";
import { Suspense, use } from "react";

import { todosQuery } from "~/queries.ts";

export const Route = createFileRoute("/todos")({
	loader: (args) => ({
		todos: args.context.queryClient.prefetch(todosQuery),
	}),
	component: TodosPage,
});
```

`prefetch` starts the request and returns immediately, so the page renders while the data is on its way. Use `ensure` instead when the page can't lay out without the data: the navigation then waits for it. [Starting queries](https://raf-query.katt.dev/docs/starting-queries.md) covers the trade-off.

## 4. Read it in a component

```tsx title="src/routes/todos.tsx"
function TodosPage() {
	const loaderData = Route.useLoaderData();
	return (
		<Suspense fallback={<p>Loading…</p>}>
			<TodoList handle={loaderData.todos} />
		</Suspense>
	);
}

function TodoList(props: { handle: QueryHandle<typeof todosQuery> }) {
	const [pagesPromise, todosState] = useQuery(todosQuery, props.handle);
	const pages = use(pagesPromise);
	return (
		<ul aria-busy={todosState.isFetching}>
			{pages.pages
				.flatMap((page) => page.items)
				.map((todo) => (
					<li key={todo.id}>{todo.title}</li>
				))}
		</ul>
	);
}
```

`useQuery` returns a stable promise and the query's controls. `use()` suspends until the data is there, then returns it; once the cache has data, the promise is already resolved and `use()` returns synchronously.

## 5. Write data with an Action

There is no `useMutation`. Write in a form action, then invalidate what changed:

```tsx title="src/components/add-todo.tsx"
export function AddTodo() {
	const qc = useQueryClient();
	return (
		<form
			action={async (formData) => {
				await createTodo({ title: String(formData.get("title")) });
				await qc.invalidate(todosQuery);
			}}
		>
			<input name="title" />
			<button>Add</button>
		</form>
	);
}
```

`await qc.invalidate(...)` resolves once every query being read has refetched, so the form stays pending until the new todo is on screen. See [Mutations](https://raf-query.katt.dev/docs/mutations.md) for optimistic updates and errors.

## What you get for free

Out of the box the cache dedupes requests, retries failures, refetches stale data when the window regains focus, garbage-collects unused entries and keeps unchanged data referentially stable. Read [Important defaults](https://raf-query.katt.dev/docs/important-defaults.md) next: they explain when raf-query fetches and when it doesn't.
