ExperimentalKitchen sink might change or go away in any release. Stability
Forms with react-hook-form
One zod schema checks a react-hook-form form in the browser and the procedure's input on the server, and matchError() puts each server error on its field.
On this page
The demo edits a profile. Its form is checked twice by one schema, in the browser before it's sent and by the procedure when it arrives, and whatever the server still rejects lands on the field it's about. Its pages load their data in the route's loader, so they render with it.
The demo runs here, in your browser.
Things to try:
- Save a one-letter username. The browser's check stops it before a request is sent.
- Turn off Check in the browser too and save it again. The request goes out, and the server's
BAD_REQUESTputs the same message on the same field. - Save
adminas the username. Only the server knows it's taken, and itsCONFLICTlands on the username field. - Press Fail next request, then save: the call fails with
NETWORK_ERROR, shown above the button. - Edit the code: change a message in
schema.ts, or the taken usernames inserver.ts, and save the form again.
One schema
The schema lives in a file both sides import. The form holds the schema's input type, before .trim() runs:
/**
* The profile form's fields. The form checks them in the browser and the
* procedure's `.input()` checks them again on the server: one schema, so
* the two can't disagree.
*/
export const const profileSchema: z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>
profileSchema = import zz.function object<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}>(shape?: {
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>
object({
name: z.ZodStringname: import zz
.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()
._ZodString<$ZodStringInternals<string>>.trim(): z.ZodStringtrim()
._ZodString<$ZodStringInternals<string>>.min(minLength: number, params?: string | z.core.$ZodCheckMinLengthParams): z.ZodStringmin(1, 'Enter your name')
._ZodString<$ZodStringInternals<string>>.max(maxLength: number, params?: string | z.core.$ZodCheckMaxLengthParams): z.ZodStringmax(40, 'At most 40 characters'),
username: z.ZodStringusername: import zz
.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()
._ZodString<$ZodStringInternals<string>>.regex(regex: RegExp, params?: string | z.core.$ZodCheckRegexParams): z.ZodStringregex(/^[a-z0-9_]{3,20}$/, '3 to 20 lowercase letters, digits or _'),
email: z.ZodEmailemail: import zz.function email(params?: string | z.core.$ZodEmailParams): z.ZodEmailemail('Enter an email address'),
bio: z.ZodStringbio: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()._ZodString<$ZodStringInternals<string>>.max(maxLength: number, params?: string | z.core.$ZodCheckMaxLengthParams): z.ZodStringmax(120, 'At most 120 characters'),
});
/** What the form holds: the schema's input, before `.trim()`. */
export type type ProfileInput = {
name: string;
username: string;
email: string;
bio: string;
}
ProfileInput = import zz.type input<T> = T extends {
_zod: {
input: any;
};
} ? T["_zod"]["input"] : unknown
export input
input<typeof const profileSchema: z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>
profileSchema>;The procedure takes the same schema as its .input(), so a request the browser didn't check is checked anyway. A taken username is something only the server can know: the resolver returns it as an error() with the field it's about in data, which puts it in the client's error type (Errors):
export const const appRouter: {
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>;
services: never;
}>;
};
}
appRouter = {
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>;
services: never;
}>;
}
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>
get: const t: TrpcRoot<{}, {}, RootDef<{}, {}>>t.TrpcRoot<{}, {}, RootDef<{}, {}>>.procedure: ProcedureBuilder<RootDef<{}, {}>>procedure.ProcedureBuilder<RootDef<{}, {}>>.query<Profile>(resolver: (opts: ResolverOpts<RootDef<{}, {}>>) => Profile): Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>
query(() => const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.profile: Profileprofile),
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>
changes: const t: TrpcRoot<{}, {}, RootDef<{}, {}>>t.TrpcRoot<{}, {}, RootDef<{}, {}>>.procedure: ProcedureBuilder<RootDef<{}, {}>>procedure.ProcedureBuilder<RootDef<{}, {}>>.query<Change[]>(resolver: (opts: ResolverOpts<RootDef<{}, {}>>) => Change[]): Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>
query(() => const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.changes: Change[]changes.Array<Change>.slice(start?: number, end?: number): Change[]slice(0, 5)),
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>;
services: never;
}>
update: const t: TrpcRoot<{}, {}, RootDef<{}, {}>>t.TrpcRoot<{}, {}, RootDef<{}, {}>>.procedure: ProcedureBuilder<RootDef<{}, {}>>procedure
// The same schema as the form's: a request that skips the browser's
// check gets a `BAD_REQUEST` with the schema's issues.
.ProcedureBuilder<RootDef<{}, {}>>.input<z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<InputDef<RootDef<{}, {}>, z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>> (+1 overload)
input(const profileSchema: z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>
profileSchema)
.ProcedureBuilder<InputDef<RootDef<{}, {}>, ZodObject<{ name: ZodString; username: ZodString; email: ZodEmail; bio: ZodString; }, $strip>>>.mutation<Profile | TrpcError<"CONFLICT", {
field: "username";
}>>(resolver: (opts: ResolverOpts<InputDef<RootDef<{}, {}>, z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>>) => Profile | TrpcError<"CONFLICT", {
field: "username";
}>): Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<...>;
services: never;
}>
mutation((opts: ResolverOpts<InputDef<RootDef<{}, {}>, z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>>
opts) => {
const const next: {
name: string;
username: string;
email: string;
bio: string;
}
next = opts: ResolverOpts<InputDef<RootDef<{}, {}>, z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>>
opts.ResolverOpts<InputDef<RootDef<{}, {}>, ZodObject<{ name: ZodString; username: ZodString; email: ZodEmail; bio: ZodString; }, $strip>>>.input: {
name: string;
username: string;
email: string;
bio: string;
}
input;
if (
const next: {
name: string;
username: string;
email: string;
bio: string;
}
next.username: stringusername !== const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.profile: Profileprofile.username: stringusername &&
const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.taken: Set<string>taken.Set<string>.has(value: string): booleanhas(const next: {
name: string;
username: string;
email: string;
bio: string;
}
next.username: stringusername)
) {
// Returned, not thrown: the client's error type includes it.
return error<"CONFLICT", {
field: "username";
}>(opts: Omit<TrpcErrorOptions<"CONFLICT", {
field: "username";
}>, "defined">): TrpcError<"CONFLICT", {
field: "username";
}>
error({
code: "CONFLICT"code: 'CONFLICT',
message?: string | undefinedmessage: `@${const next: {
name: string;
username: string;
email: string;
bio: string;
}
next.username: stringusername} is taken`,
data?: {
field: "username";
} | undefined
data: { field: "username"field: 'username' as type const = "username"const },
});
}
const const at: stringat = new var Date: DateConstructor
new () => Date (+3 overloads)
Date().Date.toISOString(): stringtoISOString();
for (const const field: "name" | "username" | "email" | "bio"field of ['name', 'username', 'email', 'bio'] as type const = readonly ["name", "username", "email", "bio"]const) {
if (const next: {
name: string;
username: string;
email: string;
bio: string;
}
next[const field: "name" | "username" | "email" | "bio"field] !== const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.profile: Profileprofile[const field: "name" | "username" | "email" | "bio"field]) {
const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.changes: Change[]changes.Array<Change>.unshift(...items: Change[]): numberunshift({
Change.at: stringat,
Change.field: "name" | "username" | "email" | "bio"field,
Change.from: stringfrom: const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.profile: Profileprofile[const field: "name" | "username" | "email" | "bio"field],
Change.to: stringto: const next: {
name: string;
username: string;
email: string;
bio: string;
}
next[const field: "name" | "username" | "email" | "bio"field],
});
}
}
const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.profile: Profileprofile = { ...const next: {
name: string;
username: string;
email: string;
bio: string;
}
next, Profile.updatedAt: stringupdatedAt: const at: stringat };
return const db: {
profile: Profile;
changes: Array<Change>;
taken: Set<string>;
}
db.profile: Profileprofile;
}),
},
};
export type type AppRouter = {
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>;
services: never;
}>;
};
}
AppRouter = typeof const appRouter: {
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<z.ZodObject<{
name: z.ZodString;
username: z.ZodString;
email: z.ZodEmail;
bio: z.ZodString;
}, z.core.$strip>>;
services: never;
}>;
};
}
appRouter;Server errors on their fields
standardSchemaResolver runs the schema in the browser. On submit, the form calls the procedure with safe(), and matchError() takes a handler for each way the call can fail, all required and each typed from the server (Handling every error):
const const resolver: Resolver<{
name: string;
username: string;
email: string;
bio: string;
}, unknown, {
name: string;
username: string;
email: string;
bio: string;
}>
resolver = standardSchemaResolver<{
name: string;
username: string;
email: string;
bio: string;
}, unknown, {
name: string;
username: string;
email: string;
bio: string;
}>(schema: StandardSchemaV1<{
name: string;
username: string;
email: string;
bio: string;
}, {
name: string;
username: string;
email: string;
bio: string;
}>, _schemaOptions?: never, resolverOptions?: {
raw?: false;
}): Resolver<{
name: string;
username: string;
email: string;
bio: string;
}, unknown, {
name: string;
username: string;
email: string;
bio: string;
}> (+1 overload)
standardSchemaResolver(const profileSchema: ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>
profileSchema);
const const form: UseFormReturn<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}>
form = useForm<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}>(props?: Partial<{
mode: Mode;
disabled: boolean;
reValidateMode: Exclude<Mode, "onTouched" | "all">;
defaultValues: AsyncDefaultValues<{
name: string;
username: string;
email: string;
bio: string;
}> | {
name?: string | undefined;
username?: string | undefined;
email?: string | undefined;
bio?: string | undefined;
};
values: {
name: string;
username: string;
email: string;
bio: string;
};
errors: FieldErrors<{
name: string;
username: string;
email: string;
bio: string;
}>;
... 10 more ...;
validate: ValidateForm<...>;
}> | undefined): UseFormReturn<...>
useForm<type ProfileInput = {
name: string;
username: string;
email: string;
bio: string;
}
ProfileInput>({
resolver?: Resolver<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}> | undefined
resolver: (values: {
name: string;
username: string;
email: string;
bio: string;
}
values, context: anycontext, options: ResolverOptions<{
name: string;
username: string;
email: string;
bio: string;
}>
options) =>
const checkInBrowser: booleancheckInBrowser
? const resolver: (values: {
name: string;
username: string;
email: string;
bio: string;
}, context: unknown, options: ResolverOptions<{
name: string;
username: string;
email: string;
bio: string;
}>) => ResolverResult<{
name: string;
username: string;
email: string;
bio: string;
}, {
name: string;
username: string;
email: string;
bio: string;
}> | Promise<ResolverResult<{
name: string;
username: string;
email: string;
bio: string;
}, {
name: string;
username: string;
email: string;
bio: string;
}>>
resolver(values: {
name: string;
username: string;
email: string;
bio: string;
}
values, context: anycontext, options: ResolverOptions<{
name: string;
username: string;
email: string;
bio: string;
}>
options)
: { values: {
name: string;
username: string;
email: string;
bio: string;
}
values, errors: {}errors: {} },
defaultValues?: AsyncDefaultValues<{
name: string;
username: string;
email: string;
bio: string;
}> | {
name?: string | undefined;
username?: string | undefined;
email?: string | undefined;
bio?: string | undefined;
} | undefined
defaultValues: {
name: stringname: const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.name: stringname,
username?: string | undefinedusername: const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.username: stringusername,
email?: string | undefinedemail: const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.email: stringemail,
bio?: string | undefinedbio: const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.bio: stringbio,
},
});
const const save: (e?: React.BaseSyntheticEvent) => Promise<void | undefined>save = const form: UseFormReturn<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}>
form.handleSubmit: <Promise<void>>(onValid: SubmitHandler<{
name: string;
username: string;
email: string;
bio: string;
}, Promise<void>>, onInvalid?: SubmitErrorHandler<{
name: string;
username: string;
email: string;
bio: string;
}> | undefined) => (e?: React.BaseSyntheticEvent) => Promise<void | undefined>
handleSubmit(async (values: {
name: string;
username: string;
email: string;
bio: string;
}
values) => {
const [const saved: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
} | undefined
saved, const err: ClientError<TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE"> | undefined
err] = await safe<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, ClientError<TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>(promise: TrpcPromise<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, ClientError<TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<...>, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>): Promise<...> (+2 overloads)
safe(const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>
client.profile: ClientRouter<{
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
}, {
...;
}, undefined, LinksTypes<...>>
profile.update: {
mutate(input: {
name: string;
username: string;
email: string;
bio: string;
}, opts?: CallOptions<{
headers?: HeadersInit | undefined;
init?: TrpcRequestInit | undefined;
batch?: false | undefined;
reconnect?: false | undefined;
onResponse?: ((meta: ResponseMeta) => void) | undefined | undefined;
}> | undefined): TrpcPromise<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, ClientError<TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>;
}
update.function mutate(input: {
name: string;
username: string;
email: string;
bio: string;
}, opts?: CallOptions<{
headers?: HeadersInit | undefined;
init?: TrpcRequestInit | undefined;
batch?: false | undefined;
reconnect?: false | undefined;
onResponse?: ((meta: ResponseMeta) => void) | undefined | undefined;
}> | undefined): TrpcPromise<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, ClientError<TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>
mutate(values: {
name: string;
username: string;
email: string;
bio: string;
}
values));
if (const err: ClientError<TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE"> | undefined
err) {
// One handler per way the call can fail, inferred from the server.
matchError<ClientError<TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">, {
BAD_REQUEST: (e: TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>> & {
...;
}) => void;
CONFLICT: (e: TrpcError<...> & {
...;
}) => void;
NETWORK_ERROR: () => void;
BAD_RESPONSE: () => void;
unexpected: (e: UndeclaredError | ... 21 more ... | (TrpcError<...> & {
...;
})) => void;
}>(err: ClientError<...>, handlers: {
BAD_REQUEST: (e: TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>> & {
...;
}) => void;
CONFLICT: (e: TrpcError<...> & {
...;
}) => void;
NETWORK_ERROR: () => void;
BAD_RESPONSE: () => void;
unexpected: (e: UndeclaredError | ... 21 more ... | (TrpcError<...> & {
...;
})) => void;
}): void
matchError(const err: ClientError<TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">
err, {
// The schema's issues, with paths typed from its input.
type BAD_REQUEST: (e: TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>> & {
readonly defined: true;
}) => void
BAD_REQUEST: (e: TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>> & {
readonly defined: true;
}
e) => {
for (const const issue: {
readonly code: "invalid_format";
readonly format: $ZodStringFormats | (string & {});
readonly pattern?: string | undefined;
readonly path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"];
readonly message: string;
} | {
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
... 4 more ...;
readonly message: string;
} | ... 8 more ... | {
...;
}
issue of e: TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>> & {
readonly defined: true;
}
e.TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{ name: ZodString; username: ZodString; email: ZodEmail; bio: ZodString; }, $strip>>>.data: SchemaInputErrorData<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>
data.SchemaInputErrorData<ZodObject<{ name: ZodString; username: ZodString; email: ZodEmail; bio: ZodString; }, $strip>>.issues: readonly ({
readonly code: "invalid_format";
readonly format: $ZodStringFormats | (string & {});
readonly pattern?: string | undefined;
readonly path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"];
readonly message: string;
} | {
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
... 4 more ...;
readonly message: string;
} | ... 8 more ... | {
...;
})[]
issues) {
const form: UseFormReturn<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}>
form.setError: (name: "name" | "username" | "email" | "bio" | ErrorNamespacePath, error: ErrorOption, options?: {
shouldFocus: boolean;
}) => void
setError(const issue: {
readonly code: "invalid_format";
readonly format: $ZodStringFormats | (string & {});
readonly pattern?: string | undefined;
readonly path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"];
readonly message: string;
} | {
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
... 4 more ...;
readonly message: string;
} | ... 8 more ... | {
...;
}
issue.path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"]path[0] ?? 'root.server', {
message?: string | undefinedmessage: const issue: {
readonly code: "invalid_format";
readonly format: $ZodStringFormats | (string & {});
readonly pattern?: string | undefined;
readonly path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"];
readonly message: string;
} | {
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["name"] | readonly ["username"] | readonly ["email"] | readonly ["bio"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
... 4 more ...;
readonly message: string;
} | ... 8 more ... | {
...;
}
issue.message: stringmessage,
});
}
},
// The resolver's returned `error()`, with its `data`.
type CONFLICT: (e: TrpcError<"CONFLICT", {
field: "username";
}> & {
readonly defined: true;
}) => void
CONFLICT: (e: TrpcError<"CONFLICT", {
field: "username";
}> & {
readonly defined: true;
}
e) => const form: UseFormReturn<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}>
form.setError: (name: "name" | "username" | "email" | "bio" | ErrorNamespacePath, error: ErrorOption, options?: {
shouldFocus: boolean;
}) => void
setError(e: TrpcError<"CONFLICT", {
field: "username";
}> & {
readonly defined: true;
}
e.TrpcError<"CONFLICT", { field: "username"; }>.data: {
field: "username";
}
data.field: "username"field, { message?: string | undefinedmessage: e: TrpcError<"CONFLICT", {
field: "username";
}> & {
readonly defined: true;
}
e.Error.message: stringmessage }),
type NETWORK_ERROR: () => voidNETWORK_ERROR: () =>
const form: UseFormReturn<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}>
form.setError: (name: "name" | "username" | "email" | "bio" | ErrorNamespacePath, error: ErrorOption, options?: {
shouldFocus: boolean;
}) => void
setError('root.server', {
message?: string | undefinedmessage: "Couldn't reach the server. Try again.",
}),
type BAD_RESPONSE: () => voidBAD_RESPONSE: () =>
const form: UseFormReturn<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}>
form.setError: (name: "name" | "username" | "email" | "bio" | ErrorNamespacePath, error: ErrorOption, options?: {
shouldFocus: boolean;
}) => void
setError('root.server', { message?: string | undefinedmessage: 'Try again later.' }),
unexpected: (e: UndeclaredError | (TrpcError<"CONFLICT", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | ... 15 more ... | (TrpcError<...> & {
readonly defined: false;
})) => void
unexpected: (e: UndeclaredError | (TrpcError<"CONFLICT", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | ... 15 more ... | (TrpcError<...> & {
readonly defined: false;
})
e) => const form: UseFormReturn<{
name: string;
username: string;
email: string;
bio: string;
}, any, {
name: string;
username: string;
email: string;
bio: string;
}>
form.setError: (name: "name" | "username" | "email" | "bio" | ErrorNamespacePath, error: ErrorOption, options?: {
shouldFocus: boolean;
}) => void
setError('root.server', { message?: string | undefinedmessage: e: UndeclaredError | (TrpcError<"CONFLICT", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | ... 15 more ... | (TrpcError<...> & {
readonly defined: false;
})
e.Error.message: stringmessage }),
});
return;
}
const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.queryClient: QueryClientqueryClient.QueryClient.setQueryData<unknown, TrpcQueryKey & {
[dataTagSymbol]: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
};
[dataTagErrorSymbol]: NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>;
}, {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}>(queryKey: TrpcQueryKey & {
[dataTagSymbol]: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
};
[dataTagErrorSymbol]: NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>;
}, updater: Updater<...>, options?: SetDataOptions): NoInfer<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}> | undefined
setQueryData(const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.trpc: TrpcQueryUtils<TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>>
trpc.profile: TrpcQueryUtils<ClientRouter<{
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
}, {
...;
}, undefined, LinksTypes<...>>>
profile.get: QueryProcedureUtils<undefined, {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, Omit<NonNullable<{
signal?: AbortSignal | undefined;
} & {
...;
}>, "signal" | "lastEventId">>
get.QueryProcedureUtils<undefined, { readonly updatedAt: string; name: string; username: string; email: string; bio: string; }, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, Omit<...>>.queryKey(opts?: {
input?: unique symbol | undefined;
} | undefined): TrpcQueryKey & {
[dataTagSymbol]: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
};
[dataTagErrorSymbol]: NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>;
}
queryKey(), const saved: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
saved);
await const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.queryClient: QueryClientqueryClient.QueryClient.invalidateQueries<TrpcQueryKey & {
[dataTagSymbol]: {
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[];
[dataTagErrorSymbol]: NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>;
}>(filters?: InvalidateQueryFilters<TrpcQueryKey & {
[dataTagSymbol]: {
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[];
[dataTagErrorSymbol]: NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>;
}> | undefined, options?: InvalidateOptions): Promise<void>
invalidateQueries(
const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.trpc: TrpcQueryUtils<TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>>
trpc.profile: TrpcQueryUtils<ClientRouter<{
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
}, {
...;
}, undefined, LinksTypes<...>>>
profile.changes: QueryProcedureUtils<undefined, {
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, Omit<NonNullable<{
signal?: AbortSignal | undefined;
} & {
...;
}>, "signal" | "lastEventId">>
changes.QueryProcedureUtils<undefined, { readonly at: string; readonly field: keyof output<typeof profileSchema>; readonly from: string; readonly to: string; }[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, Omit<...>>.queryFilter(opts?: ({
input?: undefined;
} & QueryFiltersIn) | undefined): QueryFiltersIn & {
queryKey: TrpcQueryKey & {
[dataTagSymbol]: {
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[];
[dataTagErrorSymbol]: NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>;
};
}
queryFilter(),
);
await const navigate: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, RouterContext, AnyContext, AnyContext, {}, undefined, readonly [Route<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", "/", string, "/", undefined, ResolveParams<"/">, AnyContext, AnyContext, AnyContext, {}, (opts: LoaderFnContext<unknown, RootRoute<Register, ... 10 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>, Route<...>], unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, "/", string, string, "">({ from, ...rest }: NavigateOptions<...>) => Promise<void>navigate({ RequiredToOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, RouterContext, AnyContext, AnyContext, {}, undefined, ... 4 more ..., undefined>, "never", false, RouterHistory, Record<...>>, string, "/">.to: "/" | "/edit" | "." | ".."to: '/' });
});BAD_REQUESTis the input's: itsdata.issuesare the schema's issues, and each issue'spathis typed from the input, soissue.path[0]is a field namesetError()takes without a cast (Input issues).CONFLICTis the resolver's own, withdata.fieldtyped as'username'.NETWORK_ERRORandBAD_RESPONSEarehttpLink's: every call through it can fail with them.unexpectedtakes the rest, such as a crash on the server.
Adding an error() to the resolver makes this matchError() a type error until it handles the new code, and removing one makes its handler left over a type error.
Loading in the loader
A route's loader starts the page's queries before it renders, and its component reads them. The demo has a small helper for that: prefetch() starts a query without waiting, ensure() waits for it (or takes it from the cache), and useLoaderQuery() reads what the loader started, with no second list of query options:
/**
* A query a route's loader started, for the route's components to read.
* The loader decides when each request starts and whether the page waits
* for it; components read what it started without naming the query again.
*/
export interface interface LoaderQuery<TData, TError>LoaderQuery<function (type parameter) TData in LoaderQuery<TData, TError>TData, function (type parameter) TError in LoaderQuery<TData, TError>TError> {
readonly LoaderQuery<TData, TError>.options: QueryOptionsOut<TData, TError, TData>options: type QueryOptionsOut<TQueryFnData, TError, TData> = Omit<QueryOptionsIn<TQueryFnData, TError, TData>, "enabled" | "initialData"> & {
enabled?: boolean;
queryKey: TrpcQueryKeyFor<TQueryFnData, TError>;
queryFn: QueryFunction<TQueryFnData, TrpcQueryKeyFor<TQueryFnData, TError>>;
}
QueryOptionsOut<function (type parameter) TData in LoaderQuery<TData, TError>TData, function (type parameter) TError in LoaderQuery<TData, TError>TError, function (type parameter) TData in LoaderQuery<TData, TError>TData>;
}
/**
* Starts a query and doesn't wait: the page renders, and the component
* reading it suspends until it arrives.
*/
export function function prefetch<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): LoaderQuery<TData, TError>prefetch<function (type parameter) TData in prefetch<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): LoaderQuery<TData, TError>TData, function (type parameter) TError in prefetch<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): LoaderQuery<TData, TError>TError>(
queryClient: QueryClientqueryClient: class QueryClientQueryClient,
options: QueryOptionsOut<TData, TError, TData>options: type QueryOptionsOut<TQueryFnData, TError, TData> = Omit<QueryOptionsIn<TQueryFnData, TError, TData>, "enabled" | "initialData"> & {
enabled?: boolean;
queryKey: TrpcQueryKeyFor<TQueryFnData, TError>;
queryFn: QueryFunction<TQueryFnData, TrpcQueryKeyFor<TQueryFnData, TError>>;
}
QueryOptionsOut<function (type parameter) TData in prefetch<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): LoaderQuery<TData, TError>TData, function (type parameter) TError in prefetch<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): LoaderQuery<TData, TError>TError, function (type parameter) TData in prefetch<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): LoaderQuery<TData, TError>TData>,
): interface LoaderQuery<TData, TError>LoaderQuery<function (type parameter) TData in prefetch<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): LoaderQuery<TData, TError>TData, function (type parameter) TError in prefetch<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): LoaderQuery<TData, TError>TError> {
// Its error, if any, is the reading component's to throw.
queryClient: QueryClientqueryClient.QueryClient.query<TData, TError, TData, TData, TrpcQueryKey & {
[dataTagSymbol]: TData;
[dataTagErrorSymbol]: TError;
}, never>(options: QueryExecuteOptions<TData, TError, TData, TData, TrpcQueryKey & {
[dataTagSymbol]: TData;
[dataTagErrorSymbol]: TError;
}, never>): Promise<TData>
query(options: QueryOptionsOut<TData, TError, TData>options).Promise<TData>.catch<undefined>(onrejected?: ((reason: any) => PromiseLike<undefined> | undefined) | null | undefined): Promise<TData | undefined>catch(function noop(): void (+1 overload)noop);
return { LoaderQuery<TData, TError>.options: QueryOptionsOut<TData, TError, TData>options };
}
/**
* Starts a query, or reads it from the cache however old, and the page
* waits for it. Stale data refetches once a component reads it.
*/
export async function function ensure<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): Promise<LoaderQuery<TData, TError>>ensure<function (type parameter) TData in ensure<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): Promise<LoaderQuery<TData, TError>>TData, function (type parameter) TError in ensure<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): Promise<LoaderQuery<TData, TError>>TError>(
queryClient: QueryClientqueryClient: class QueryClientQueryClient,
options: QueryOptionsOut<TData, TError, TData>options: type QueryOptionsOut<TQueryFnData, TError, TData> = Omit<QueryOptionsIn<TQueryFnData, TError, TData>, "enabled" | "initialData"> & {
enabled?: boolean;
queryKey: TrpcQueryKeyFor<TQueryFnData, TError>;
queryFn: QueryFunction<TQueryFnData, TrpcQueryKeyFor<TQueryFnData, TError>>;
}
QueryOptionsOut<function (type parameter) TData in ensure<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): Promise<LoaderQuery<TData, TError>>TData, function (type parameter) TError in ensure<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): Promise<LoaderQuery<TData, TError>>TError, function (type parameter) TData in ensure<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): Promise<LoaderQuery<TData, TError>>TData>,
): interface Promise<T>Promise<interface LoaderQuery<TData, TError>LoaderQuery<function (type parameter) TData in ensure<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): Promise<LoaderQuery<TData, TError>>TData, function (type parameter) TError in ensure<TData, TError>(queryClient: QueryClient, options: QueryOptionsOut<TData, TError, TData>): Promise<LoaderQuery<TData, TError>>TError>> {
await queryClient: QueryClientqueryClient.QueryClient.query<TData, TError, TData, TData, TrpcQueryKey & {
[dataTagSymbol]: TData;
[dataTagErrorSymbol]: TError;
}, never>(options: QueryExecuteOptions<TData, TError, TData, TData, TrpcQueryKey & {
[dataTagSymbol]: TData;
[dataTagErrorSymbol]: TError;
}, never>): Promise<TData>
query({ ...options: QueryOptionsOut<TData, TError, TData>options, QueryExecuteOptions<TData, TError, TData, TData, TrpcQueryKey & { [dataTagSymbol]: TData; [dataTagErrorSymbol]: TError; }, never>.staleTime?: StaleTimeFunction<TData, TError, TData, TrpcQueryKey & {
[dataTagSymbol]: TData;
[dataTagErrorSymbol]: TError;
}> | undefined
staleTime: 'static' });
return { LoaderQuery<TData, TError>.options: QueryOptionsOut<TData, TError, TData>options };
}
/**
* The data of a query the loader started. It stays subscribed, so an
* invalidation or a `setQueryData` re-renders with the new data.
*/
export function function useLoaderQuery<TData, TError>(query: LoaderQuery<TData, TError>): TDatauseLoaderQuery<function (type parameter) TData in useLoaderQuery<TData, TError>(query: LoaderQuery<TData, TError>): TDataTData, function (type parameter) TError in useLoaderQuery<TData, TError>(query: LoaderQuery<TData, TError>): TDataTError>(
query: LoaderQuery<TData, TError>query: interface LoaderQuery<TData, TError>LoaderQuery<function (type parameter) TData in useLoaderQuery<TData, TError>(query: LoaderQuery<TData, TError>): TDataTData, function (type parameter) TError in useLoaderQuery<TData, TError>(query: LoaderQuery<TData, TError>): TDataTError>,
): function (type parameter) TData in useLoaderQuery<TData, TError>(query: LoaderQuery<TData, TError>): TDataTData {
return useSuspenseQuery<TData, TError, TData, TrpcQueryKey & {
[dataTagSymbol]: TData;
[dataTagErrorSymbol]: TError;
}>(options: UseSuspenseQueryOptions<TData, TError, TData, TrpcQueryKey & {
[dataTagSymbol]: TData;
[dataTagErrorSymbol]: TError;
}>, queryClient?: QueryClient): UseSuspenseQueryResult<TData, TError>
useSuspenseQuery(query: LoaderQuery<TData, TError>query.LoaderQuery<TData, TError>.options: QueryOptionsOut<TData, TError, TData>options).data: TDatadata;
}The profile page waits for the profile, and lets the list of recent changes arrive while it shows. With the network on Slow, the page appears with the profile and a placeholder where the changes come in:
export const const profileRoute: Route<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", "/", string, "/", undefined, ResolveParams<"/">, AnyContext, AnyContext, AnyContext, {}, (opts: LoaderFnContext<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", ResolveParams<"/">, ... 5 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>profileRoute = createRoute<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", "/", string, "/", undefined, ResolveParams<"/">, AnyContext, AnyContext, {}, (opts: LoaderFnContext<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", ResolveParams<"/">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>) => Promise<...>, unknown, unknown, unknown>(options: RouteOptions<...>): Route<...>createRoute({
getParentRoute: () => RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>getParentRoute: () => const rootRoute: RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>rootRoute,
path: "/"path: '/',
loader: (opts: LoaderFnContext<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", ResolveParams<"/">, ... 5 more ..., undefined>) => Promise<...>loader: async (opts: LoaderFnContext<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", ResolveParams<"/">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>opts) => {
const const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx = opts: LoaderFnContext<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", ResolveParams<"/">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>opts.LoaderFnContext<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, ... 7 more ..., undefined>.context: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
context;
// Starts now and isn't waited for: <Changes> suspends until it arrives.
const const changes: LoaderQuery<{
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>
changes = prefetch<{
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>(queryClient: QueryClient, options: QueryOptionsOut<{
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, {
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[]>): LoaderQuery<...>
prefetch(
const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.queryClient: QueryClientqueryClient,
const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.trpc: TrpcQueryUtils<TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>>
trpc.profile: TrpcQueryUtils<ClientRouter<{
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
}, {
...;
}, undefined, LinksTypes<...>>>
profile.changes: QueryProcedureUtils<undefined, {
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, Omit<NonNullable<{
signal?: AbortSignal | undefined;
} & {
...;
}>, "signal" | "lastEventId">>
changes.QueryProcedureUtils<undefined, { readonly at: string; readonly field: keyof output<typeof profileSchema>; readonly from: string; readonly to: string; }[], NonNullable<...>, Omit<...>>.queryOptions<{
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[]>(opts?: ({
input?: unique symbol | undefined;
} & ContextBag<Omit<NonNullable<{
signal?: AbortSignal | undefined;
} & {
headers?: HeadersInit | undefined;
init?: TrpcRequestInit | undefined;
batch?: false | undefined;
reconnect?: false | undefined;
onResponse?: ((meta: ResponseMeta) => void) | undefined | undefined;
}>, "signal" | "lastEventId">> & QueryOptionsIn<...>) | undefined): QueryOptionsOut<...> (+1 overload)
queryOptions(),
);
// The page waits for the profile, so it renders with it.
const const profile: LoaderQuery<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>
profile = await ensure<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>(queryClient: QueryClient, options: QueryOptionsOut<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}>): Promise<...>
ensure(
const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.queryClient: QueryClientqueryClient,
const ctx: {
readonly queryClient: QueryClient;
client: TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>;
trpc: TrpcQueryUtils<...>;
}
ctx.trpc: TrpcQueryUtils<TrpcClient<{
profile: {
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>>
trpc.profile: TrpcQueryUtils<ClientRouter<{
get: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Profile;
errors: never;
services: never;
}>;
changes: Procedure<{
type: "query";
ctx: {};
meta: {};
input: undefined;
output: Change[];
errors: never;
services: never;
}>;
update: Procedure<{
type: "mutation";
ctx: {};
meta: {};
input: {
name: string;
username: string;
email: string;
bio: string;
};
output: Profile;
errors: TrpcError<"CONFLICT", {
field: "username";
}> | SchemaValidationError<ZodObject<{
name: ZodString;
username: ZodString;
email: ZodEmail;
bio: ZodString;
}, $strip>>;
services: never;
}>;
}, {
...;
}, undefined, LinksTypes<...>>>
profile.get: QueryProcedureUtils<undefined, {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, Omit<NonNullable<{
signal?: AbortSignal | undefined;
} & {
...;
}>, "signal" | "lastEventId">>
get.QueryProcedureUtils<undefined, { readonly updatedAt: string; name: string; username: string; email: string; bio: string; }, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>, Omit<...>>.queryOptions<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}>(opts?: ({
input?: unique symbol | undefined;
} & ContextBag<Omit<NonNullable<{
signal?: AbortSignal | undefined;
} & {
headers?: HeadersInit | undefined;
init?: TrpcRequestInit | undefined;
batch?: false | undefined;
reconnect?: false | undefined;
onResponse?: ((meta: ResponseMeta) => void) | undefined | undefined;
}>, "signal" | "lastEventId">> & QueryOptionsIn<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<...>, {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}>) | undefined): QueryOptionsOut<...> (+1 overload)
queryOptions(),
);
return { changes: LoaderQuery<{
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>
changes, profile: LoaderQuery<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>
profile };
},
UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function ProfilePage(): ReactNodeProfilePage,
});
function function ProfilePage(): ReactNodeProfilePage(): type ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefinedReactNode {
const const data: {
changes: LoaderQuery<{
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>;
profile: LoaderQuery<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>;
}
data = const profileRoute: Route<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", "/", string, "/", undefined, ResolveParams<"/">, AnyContext, AnyContext, AnyContext, {}, (opts: LoaderFnContext<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", ResolveParams<"/">, ... 5 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>profileRoute.Route<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, ... 15 more ..., undefined>.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, RouterContext, AnyContext, AnyContext, {}, undefined, readonly [Route<unknown, RootRoute<Register, undefined, RouterContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", "/", string, "/", undefined, ResolveParams<"/">, AnyContext, AnyContext, AnyContext, {}, (opts: LoaderFnContext<unknown, RootRoute<Register, ... 10 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>, Route<...>], unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
const const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile = useLoaderQuery<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>(query: LoaderQuery<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>): {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
useLoaderQuery(const data: {
changes: LoaderQuery<{
readonly at: string;
readonly field: keyof output<typeof profileSchema>;
readonly from: string;
readonly to: string;
}[], NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>;
profile: LoaderQuery<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>;
}
data.profile: LoaderQuery<{
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}, NonNullable<ClientError<never, JsonForm, "NETWORK_ERROR" | "BAD_RESPONSE">>>
profile);
return (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="grid gap-5">
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="flex items-center gap-3">
<React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span React.HTMLAttributes<T>.className?: string | undefinedclassName="grid size-12 place-items-center rounded-full bg-brand/15 text-lg font-semibold text-brand">
{const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.name: stringname.String.charAt(pos: number): stringcharAt(0)}
</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="grid">
<React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span React.HTMLAttributes<T>.className?: string | undefinedclassName="font-semibold">{const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.name: stringname}</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>
<React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span React.HTMLAttributes<T>.className?: string | undefinedclassName="text-sm text-muted-foreground">
@{const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.username: stringusername} · {const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.email: stringemail}
</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p React.HTMLAttributes<T>.className?: string | undefinedclassName="text-sm">{const profile: {
readonly updatedAt: string;
name: string;
username: string;
email: string;
bio: string;
}
profile.bio: stringbio}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense React.SuspenseProps.fallback?: ReactNodefallback={<function Skeleton({ className, ...props }: React.ComponentProps<"div">): React.JSX.ElementSkeleton React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="h-16 w-full" />}>
<function Changes(): ReactNodeChanges />
</const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
}The edit page's loader waits for the profile too, so the form's default values are there when it first renders. After a save, the form puts the saved profile in the cache with setQueryData(), invalidates the changes, and navigates to the profile page, which shows the new profile without a request and refetches the changes.