Enable both `useQuery` and a raw `query` from the frontend (for use in async validation)

Hello everyone, some context:
. I'm building a project with a variant of the t3 stack (key point: using trpc layered on tanstack query: trpc.xxx.yyy.useQuery({zzz}) )

. I'm using zod with react-native-forms, using the zodResolver (key point: form validation through zod, can't add external validation without a lot of hackiness)

. Schema Looks like this:
const userDataSchema = z.object({
email: z
.string()
.min(1, { message: "Email is required" })
.email({ message: "Must be a valid email" })
.superRefine(debouncedEmailUniquenessValidator),
isDebug: z.boolean().default(true),
online: z.boolean().nullish(),
});

. Validation looks like this (called by the above debouncedEmailUniquenessValidator):
const mockDbEmailCheck = async (email: string) => {
await delay(250);
return !["taken@gmail.com", "takentaken@gmail.com"].includes(email);
};
. I'm building a project with a variant of the t3 stack (key point: using trpc layered on tanstack query: trpc.xxx.yyy.useQuery({zzz}) )

. I'm using zod with react-native-forms, using the zodResolver (key point: form validation through zod, can't add external validation without a lot of hackiness)

. Schema Looks like this:
const userDataSchema = z.object({
email: z
.string()
.min(1, { message: "Email is required" })
.email({ message: "Must be a valid email" })
.superRefine(debouncedEmailUniquenessValidator),
isDebug: z.boolean().default(true),
online: z.boolean().nullish(),
});

. Validation looks like this (called by the above debouncedEmailUniquenessValidator):
const mockDbEmailCheck = async (email: string) => {
await delay(250);
return !["taken@gmail.com", "takentaken@gmail.com"].includes(email);
};
Question: In mockDbEmailCheck, I'd like to be able to run a raw query against trpc: something like const emailIsUnique = await trpc.xxx.emailIsUnique.query({email}). Is there a way to execute raw trpc queries from the frontend, as well as maintain the useQuery tanstack query wrapper? Essentially is it possible to create this:
const validateUniqueEmail = async (email: string) => {
return await trpc.user.validate.uniqueEmail.query({ email }) <<=====
};

const userEmailSchema = z.object({
email: z
.string()
.refine(validateUniqueEmail, { message: "Email must be unique" }),
})

...

const UserEmailForm = ({ userId }: { userId: string }) => {
const [data] = trpc.user.something.useQuery({ userId }) <<=====

return (
<Form schema={userEmailSchema}>
...
</Form
)
}
const validateUniqueEmail = async (email: string) => {
return await trpc.user.validate.uniqueEmail.query({ email }) <<=====
};

const userEmailSchema = z.object({
email: z
.string()
.refine(validateUniqueEmail, { message: "Email must be unique" }),
})

...

const UserEmailForm = ({ userId }: { userId: string }) => {
const [data] = trpc.user.something.useQuery({ userId }) <<=====

return (
<Form schema={userEmailSchema}>
...
</Form
)
}
Thanks everyone!
3 Replies
Nick
Nick15mo ago
How come you're using a query for sending data? It's best to use a mutation, and then react-query does give you an imperative API you can use
𝚁𝚘𝚐𝚞𝚎 𝙰𝚕𝚎𝚡𝚊𝚗𝚍𝚎𝚛
Ah I'm not, I'm using a mutation in the form submit. Thats just a oversight in the example code. I'm trying to use the query in the zod schema, where I need it to be an imperative call
Nick
Nick15mo ago
Yes if you need an imperative call, use a mutation In general Queries are for getting data with simple parameters, mutations are for sending data. A validation procedure is more of a mutation