Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

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_REQUEST puts the same message on the same field.
  • Save admin as the username. Only the server knows it's taken, and its CONFLICT lands 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 in server.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:

schema.ts
/**
 * 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):

server.ts
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):

edit-profile.tsx
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_REQUEST is the input's: its data.issues are the schema's issues, and each issue's path is typed from the input, so issue.path[0] is a field name setError() takes without a cast (Input issues).
  • CONFLICT is the resolver's own, with data.field typed as 'username'.
  • NETWORK_ERROR and BAD_RESPONSE are httpLink's: every call through it can fail with them.
  • unexpected takes 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:

loader-query.ts
/**
 * 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:

routes.tsx
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.