# Effect services

> Effect queryFns can require services. They become part of the factory's type, and QueryClient provides them from a layer.

A `queryFn`'s Effect can require [services](https://effect.website/docs/v4/requirements-management/services). They're part of the factory's type, `ServicesOf<TFactory>`, and every verb that can start the query requires them:

```ts
class IssueApi extends Context.Service<
	IssueApi,
	{ readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound> }
>()("app/IssueApi") {}

const IssueApiLive = Layer.succeed(IssueApi, { get: IssueRepo.get });

export function issueQuery(input: { id: number }) {
	return queryOptions({
		queryKey: ["issue", input.id],
		queryFn: () => IssueApi.use((api) => api.get(input.id)),
	});
}

// Effect<Issue, IssueNotFound | Query.CancelledError, QueryCache | IssueApi>
const issue = QueryCache.QueryCache.use((cache) =>
	cache.fetch(issueQuery, { id: 1 }),
);

Effect.runFork(
	issue.pipe(Effect.provide(QueryCache.layer()), Effect.provide(IssueApiLive)),
);
```

## Which services a request runs with

- On `QueryCache`, `ensure`, `fetch`, `prefetch`, `getQuery` and `watch` require the factory's services, and so does `Query.fetch`.
- A request runs with the services the cache was built with, under those of the latest of these calls for its entry.
- Requests that start without a caller (a render's read, `refetch`, polling, focus and reconnect, `invalidate`) reuse the entry's last services.
- The caller's [`Scope`](https://effect.website/docs/v4/resource-management/scope) isn't passed on: a request outlives the call that started it. Scope a `queryFn`'s resources with [`Effect.scoped`](https://effect.website/docs/v4/api/effect/Effect#scoped).

## Tracing

Each request runs in a `raf-query.request` [span](https://effect.website/docs/v4/observability/tracing) with a `raf_query.hash` attribute, under the latest caller's span if it had one. Defects are logged with [`Effect.logError`](https://effect.website/docs/v4/api/effect/Effect#logError); typed failures are the query's error.

## Services in QueryClient

`QueryClient` gets its services from a [`layer`](https://effect.website/docs/v4/requirements-management/layers). It's built into the client's scope along with the cache, so it must build synchronously:

```ts
const queryClient = new QueryClient({ layer: IssueApiLive }); // QueryClient<IssueApi>
const handle = queryClient.prefetch(issueQuery, { id: 1 });

await queryClient.dispose();
```

The client's type is `QueryClient<TServices>`, and its verbs accept only factories whose services the layer provides. Any other factory is a type error. `dispose()` interrupts every request and runs the layer's finalizers.

`queryClient.runPromise(effect)` runs an app's own Effect (a loader, a form action) with the cache and the layer's services, so it can `yield*` both:

```ts
await queryClient.runPromise(
	Effect.gen(function* () {
		const api = yield* IssueApi;
		const cache = yield* QueryCache.QueryCache;
		yield* api.rename({ id: 1, title: "Renamed" });
		yield* cache.invalidate({ queryKey: ["issue", 1] });
	}),
);
```

Like [`Effect.runPromise`](https://effect.website/docs/v4/api/effect/Effect#runPromise), it rejects with the Effect's failure or defect. An Effect that needs a service the layer doesn't provide is a type error. [Typed errors](https://raf-query.katt.dev/docs/effect-errors.md) covers catching the failures first.

### In React

`QueryClientProvider` takes a client with any services. A hook can't know which ones, so `useQueryClient()` returns a `QueryClient<unknown>`, whose verbs accept any factory and any Effect. A request then fails with a defect if the client's layer lacks a service it needs.

Components rarely need the services anyway. `useQuery` reads what a loader started, and the cache runs every request with the layer's services: the loader's, a render's, a refetch, an invalidation. So keep the check where Effects start: type the router context with the client's services, and loaders and actions get the typed client from it.

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

import type { IssueApi } from "~/issues.ts";

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

Loaders start queries through the typed client, so a factory that needs a service the layer doesn't provide is a type error there. Components read the handles with `useQuery`, which needs no services:

```tsx title="src/routes/issues.$issueId.tsx"
export const Route = createFileRoute("/issues/$issueId")({
	loader: (args) =>
		args.context.queryClient.runPromise(loadIssue(Number(args.params.issueId))),
	// (property) queryClient: QueryClient<IssueApi>
	component: IssuePage,
	errorComponent: (props) => (
		<div role="alert">
			<p>Couldn't load the issue.</p>
			<button onClick={props.reset}>Try again</button>
		</div>
	),
});

function IssuePage() {
	const loaderData = Route.useLoaderData();
	// Loader handled IssueNotFound; throw any other failure to errorComponent.
	const [issue] = useQuery(issueQuery, loaderData.issue, {
		throwOnError: true,
	});
	return (
		<article>
			<h1>{issue.title}</h1>
			<RenameForm issue={issue} />
			<Suspense fallback={<p>Loading assignee…</p>}>
				<Assignee handle={loaderData.assignee} />
			</Suspense>
		</article>
	);
}
```

A component that starts an Effect itself, like a form action, gets the client from the route's context too, so `runPromise` keeps the check:

```tsx title="src/components/rename-form.tsx"
export function RenameForm(props: { issue: Issue }) {
	const queryClient = Route.useRouteContext().queryClient; // not useQueryClient()
	return (
		<form
			action={(formData) =>
				queryClient.runPromise(
					IssueApi.use((api) =>
						api.rename({
							id: props.issue.id,
							title: String(formData.get("title")),
						}),
					).pipe(Effect.ignore),
				)
			}
		>
			<input defaultValue={props.issue.title} name="title" />
		</form>
	);
}
```

[Using Effect](https://raf-query.katt.dev/docs/effect.md#the-whole-picture) has the whole app: service, queries, client, loader, components and action.
