Integrations
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. They're part of the factory's type, ServicesOf<TFactory>, and every verb that can start the query requires them:
class class IssueApiIssueApi extends import ContextContext.const Service: <IssueApi, {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}>() => <Identifier, E, R, Args>(id: Identifier, options?: {
readonly make?: ((...args: Args) => Effect.Effect<{
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}, E, R>) | Effect.Effect<{
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}, E, R> | undefined;
} | undefined) => Context.ServiceClass<IssueApi, Identifier, {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}> & ([...] extends [...] ? unknown : {
...;
}) (+2 overloads)
Service<
class IssueApiIssueApi,
{ readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>get: (id: numberid: number) => import EffectEffect.interface Effect<out A, out E = never, out R = never>Effect<Issue, class IssueNotFoundIssueNotFound> }
>()("app/IssueApi") {}
const const IssueApiLive: Layer.Layer<IssueApi, never, never>IssueApiLive = import LayerLayer.const succeed: <IssueApi, {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}>(service: Context.Key<IssueApi, {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}>, resource: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}) => Layer.Layer<IssueApi, never, never> (+1 overload)
succeed(class IssueApiIssueApi, { get: (id: number) => Effect.Effect<Issue, IssueNotFound>get: const IssueRepo: {
get(id: number): Effect.Effect<Issue, IssueNotFound>;
}
IssueRepo.function get(id: number): Effect.Effect<Issue, IssueNotFound>get });
export function function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>
issueQuery(input: {
id: number;
}
input: { id: numberid: number }) {
return queryOptions<Issue, IssueNotFound, IssueApi, readonly ["issue", number], undefined>(options: Omit<QueryOptions<{
data: Issue;
error: IssueNotFound;
key: readonly ["issue", number];
services: IssueApi;
}>, "kind" | "throwOnError"> & ThrowOnErrorField<undefined>): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>
queryOptions({
queryKey: readonly ["issue", number]queryKey: ["issue", input: {
id: number;
}
input.id: numberid],
function queryFn(context: QueryFunctionContext): QueryFnResult<{
data: Issue;
error: IssueNotFound;
key: readonly ["issue", number];
services: IssueApi;
}>
queryFn: () => class IssueApiIssueApi.Service<IssueApi, { readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>; }>.use<Issue, IssueNotFound, never>(f: (service: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}) => Effect.Effect<Issue, IssueNotFound, never>): Effect.Effect<Issue, IssueNotFound, IssueApi>
use((api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}
api) => api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound>;
}
api.get: (id: number) => Effect.Effect<Issue, IssueNotFound>get(input: {
id: number;
}
input.id: numberid)),
});
}
// Effect<Issue, IssueNotFound | Query.CancelledError, QueryCache | IssueApi>
const const issue: Effect.Effect<QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>, IssueNotFound | CancelledError, IssueApi | QueryCache.QueryCache>
issue = import QueryCacheQueryCache.const QueryCache: Context.Service<QueryCache.QueryCache, QueryCache.QueryCache>QueryCache.Service<QueryCache, QueryCache>.use<QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>, IssueNotFound | CancelledError, IssueApi>(f: (service: QueryCache.QueryCache) => Effect.Effect<QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>, IssueNotFound | CancelledError, IssueApi>): Effect.Effect<...>
use((cache: QueryCache.QueryCachecache) =>
cache: QueryCache.QueryCachecache.QueryCache.fetch<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>(factory: (input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>, input: {
id: number;
}): Loaded<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>> (+1 overload)
fetch(function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>
issueQuery, { id: numberid: 1 }),
);
import EffectEffect.const runFork: <QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>, IssueNotFound | CancelledError>(effect: Effect.Effect<QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>, IssueNotFound | CancelledError, never>, options?: Effect.RunOptions | undefined) => Fiber<...>
runFork(
const issue: Effect.Effect<QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>, IssueNotFound | CancelledError, IssueApi | QueryCache.QueryCache>
issue.Pipeable.pipe<Effect.Effect<QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>, IssueNotFound | CancelledError, IssueApi | QueryCache.QueryCache>, Effect.Effect<QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound;
} & {
services: IssueApi;
}>>>, IssueNotFound | CancelledError, IssueApi>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe(import EffectEffect.const provide: <QueryCache.QueryCache, never, never>(layer: Layer.Layer<QueryCache.QueryCache, never, never>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, QueryCache.QueryCache>> (+5 overloads)
provide(import QueryCacheQueryCache.const layer: (config?: QueryCache.Config) => Layer.Layer<QueryCache.QueryCache>layer()), import EffectEffect.const provide: <IssueApi, never, never>(layer: Layer.Layer<IssueApi, never, never>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, IssueApi>> (+5 overloads)
provide(const IssueApiLive: Layer.Layer<IssueApi, never, never>IssueApiLive)),
);Which services a request runs with
- On
QueryCache,ensure,fetch,prefetch,getQueryandwatchrequire the factory's services, and so doesQuery.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
Scopeisn't passed on: a request outlives the call that started it. Scope aqueryFn's resources withEffect.scoped.
Tracing
Each request runs in a raf-query.request span with a raf_query.hash attribute, under the latest caller's span if it had one. Defects are logged with Effect.logError; typed failures are the query's error.
Services in QueryClient
QueryClient gets its services from a layer. It's built into the client's scope along with the cache, so it must build synchronously:
const const queryClient: QueryClient<IssueApi>queryClient = new new QueryClient<IssueApi>(config?: QueryClientConfig<IssueApi>): QueryClient<IssueApi>QueryClient({ QueryClientConfig<IssueApi>.layer?: Layer<IssueApi, never, never> | undefinedlayer: const IssueApiLive: Layer<IssueApi, never, never>IssueApiLive }); // QueryClient<IssueApi>
const const handle: QueryHandle<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
handle = const queryClient: QueryClient<IssueApi>queryClient.QueryClient<IssueApi>.prefetch<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(factory: (input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, input: {
id: number;
}): QueryHandle<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
prefetch(function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery, { id: numberid: 1 });
await const queryClient: QueryClient<IssueApi>queryClient.QueryClient<IssueApi>.dispose(): Promise<void>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:
await const queryClient: QueryClient<IssueApi>queryClient.QueryClient<IssueApi>.runPromise<void, Unauthorized | EmptyTitle>(effect: Effect.Effect<void, Unauthorized | EmptyTitle, IssueApi | QueryCache.QueryCache>): Promise<void>runPromise(
import EffectEffect.const gen: <Effect.Effect<Issue, Unauthorized | EmptyTitle, never> | Effect.Effect<{
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}, never, IssueApi> | Effect.Effect<...> | Effect.Effect<...>, void>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)
gen(function* () {
const const api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}
api = yield* class IssueApiIssueApi;
const const cache: QueryCache.QueryCachecache = yield* import QueryCacheQueryCache.const QueryCache: Service<QueryCache.QueryCache, QueryCache.QueryCache>QueryCache;
yield* const api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}
api.rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>
rename({ id: numberid: 1, title: stringtitle: "Renamed" });
yield* const cache: QueryCache.QueryCachecache.QueryCache.invalidate<QueryFactory>(...target: QueryCache.Target<QueryFactory>): Effect.Effect<void>invalidate({ Filter.queryKey?: QueryKey | undefinedqueryKey: ["issue", 1] });
}),
);Like 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 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.
import type { class QueryClient<TServices = never>QueryClient } from "@raf-query/react";
import { function createRootRouteWithContext<TRouterContext extends {}>(): <TRegister = Register, TRouteContextFn = AnyContext, TBeforeLoadFn = AnyContext, TSearchValidator = undefined, TLoaderDeps extends Record<string, any> = {}, TLoaderFn = undefined, TSSR = unknown, TServerMiddlewares = unknown>(options?: RootRouteOptions<TRegister, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TSSR, TServerMiddlewares>) => RootRoute<TRegister, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, unknown, unknown, TSSR, TServerMiddlewares, undefined>createRootRouteWithContext } from "@tanstack/react-router";
import type { class IssueApiIssueApi } from "~/issues.ts";
export const const Route: RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>
Route = createRootRouteWithContext<{
queryClient: QueryClient<IssueApi>;
}>(): <TRegister, TRouteContextFn, TBeforeLoadFn, TSearchValidator, TLoaderDeps, TLoaderFn, TSSR, TServerMiddlewares>(options?: RootRouteOptions<TRegister, TSearchValidator, {
queryClient: QueryClient<IssueApi>;
}, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TSSR, TServerMiddlewares, undefined> | undefined) => RootRoute<...>
createRootRouteWithContext<{
queryClient: QueryClient<IssueApi>queryClient: class QueryClient<TServices = never>QueryClient<class IssueApiIssueApi>;
}>()({});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:
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", "/issues/$issueId", "/issues/$issueId", "/issues/$issueId", undefined, ResolveParams<"/issues/$issueId">, AnyContext, AnyContext, AnyContext, {}, (args: LoaderFnContext<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route = createFileRoute<"/issues/$issueId", RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", "/issues/$issueId", "/issues/$issueId">(path?: "/issues/$issueId" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute("/issues/$issueId")({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient<IssueApi>; }, AnyContext, ... 7 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<(args: LoaderFnContext<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefinedloader: (args: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", ResolveParams<"/issues/$issueId">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>
args) =>
args: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", ResolveParams<"/issues/$issueId">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>
args.LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient<IssueApi>; }, AnyContext, ... 7 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient<IssueApi>;
}
context.queryClient.QueryClient<IssueApi>.runPromise<{
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}, Unauthorized | ... 1 more ... | NotFoundError>(effect: Effect.Effect<...>): Promise<...>
runPromise(const loadIssue: (id: number) => Effect.Effect<{
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}, Unauthorized | ... 1 more ... | NotFoundError, IssueApi | QueryCache.QueryCache>
loadIssue(var Number: NumberConstructor
(value?: any) => number
Number(args: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", ResolveParams<"/issues/$issueId">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>
args.LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient<IssueApi>; }, AnyContext, ... 7 more ..., undefined>, ... 7 more ..., undefined>.params: {
issueId: string;
}
params.issueId: stringissueId))), UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function IssuePage(): React.JSX.ElementIssuePage,
UpdatableRouteOptionsExtensions.errorComponent?: false | ErrorRouteComponent | null | undefinederrorComponent: (props: ErrorComponentPropsprops) => (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.role?: React.AriaRole | undefinedrole="alert">
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Couldn't load the issue.</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={props: ErrorComponentPropsprops.reset: () => voidreset}>Try again</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
),
});
function function IssuePage(): React.JSX.ElementIssuePage() {
const const loaderData: {
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", "/issues/$issueId", "/issues/$issueId", "/issues/$issueId", undefined, ResolveParams<"/issues/$issueId">, AnyContext, AnyContext, AnyContext, {}, (args: LoaderFnContext<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route.RouteExtensions<"/issues/$issueId", "/issues/$issueId">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>, "/issues/$issueId", true, unknown, boolean> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
// Loader handled IssueNotFound; throw any other failure to errorComponent.
const [const issue: Issueissue] = useQuery<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, true>(factory: (input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, handle: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, options?: (Options & ... 1 more ... & {
...;
}) | undefined): readonly [data: ...] (+1 overload)
useQuery(function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery, const loaderData: {
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}
loaderData.issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
issue, {
throwOnError?: true | undefinedthrowOnError: true,
});
return (
<React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
<React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>{const issue: Issueissue.Issue.title: stringtitle}</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>
<function RenameForm(props: {
issue: Issue;
}): React.JSX.Element
RenameForm issue: Issueissue={const issue: Issueissue} />
<const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense React.SuspenseProps.fallback?: React.ReactNodefallback={<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Loading assignee…</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>}>
<function Assignee(props: {
handle: QueryHandle<typeof assigneeQuery>;
}): React.JSX.Element
Assignee handle: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
handle={const loaderData: {
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}
loaderData.assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
assignee} />
</const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense>
</React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>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:
export function function RenameForm(props: {
issue: Issue;
}): React.JSX.Element
RenameForm(props: {
issue: Issue;
}
props: { issue: Issueissue: Issue }) {
const const queryClient: QueryClient<IssueApi>queryClient = const Route: RouteApi<"/issues/$issueId", RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>>
Route.RouteApi<"/issues/$issueId", RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, { queryClient: QueryClient<IssueApi>; }, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>>.useRouteContext: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>, unknown>(opts?: UseRouteContextBaseOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>, "/issues/$issueId", true, unknown> | undefined) => {
...;
}
useRouteContext().queryClient: QueryClient<IssueApi>queryClient; // not useQueryClient()
return (
<React.JSX.IntrinsicElements.form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>form
React.FormHTMLAttributes<HTMLFormElement>.action?: string | ((formData: FormData) => void | Promise<void>) | undefinedaction={(formData: FormDataformData) =>
const queryClient: QueryClient<IssueApi>queryClient.QueryClient<IssueApi>.runPromise<void, never>(effect: Effect.Effect<void, never, IssueApi | QueryCache>): Promise<void>runPromise(
class IssueApiIssueApi.Service<IssueApi, { readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>; readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>; readonly rename: (input: { readonly id: number; readonly title: string; }) => Effect.Effect<Issue, EmptyTitle | Unauthorized>; }>.use<Issue, Unauthorized | EmptyTitle, never>(f: (service: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}) => Effect.Effect<...>): Effect.Effect<...>
use((api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}
api) =>
api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}
api.rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>
rename({
id: numberid: props: {
issue: Issue;
}
props.issue: Issueissue.Issue.id: numberid,
title: stringtitle: var String: StringConstructor
(value?: any) => string
String(formData: FormDataformData.FormData.get(name: string): FormDataEntryValue | nullget("title")),
}),
).Pipeable.pipe<Effect.Effect<Issue, Unauthorized | EmptyTitle, IssueApi>, Effect.Effect<void, never, IssueApi>>(this: Effect.Effect<Issue, Unauthorized | EmptyTitle, IssueApi>, ab: (_: Effect.Effect<Issue, Unauthorized | EmptyTitle, IssueApi>) => Effect.Effect<void, never, IssueApi>): Effect.Effect<void, never, IssueApi> (+21 overloads)pipe(import EffectEffect.const ignore: <Arg extends Effect.Effect<any, any, any> | {
readonly log?: boolean | Severity | undefined;
readonly message?: string | undefined;
} | undefined = {
readonly log?: boolean | Severity | undefined;
readonly message?: string | undefined;
}>(effectOrOptions?: Arg, options?: {
readonly log?: boolean | Severity | undefined;
readonly message?: string | undefined;
} | undefined) => [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ? Effect.Effect<void, never, _R> : <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void, never, R>
ignore),
)
}
>
<React.JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>input React.HTMLAttributes<HTMLInputElement>.defaultValue?: string | number | readonly string[] | undefineddefaultValue={props: {
issue: Issue;
}
props.issue: Issueissue.Issue.title: stringtitle} React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefinedname="title" />
</React.JSX.IntrinsicElements.form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>form>
);
}Using Effect has the whole app: service, queries, client, loader, components and action.