Uncaught Error: Async refinement encountered during synchronous parse operation. Use .parseAsync instead. #393
-
I managed to get Next.js working but faced an error while trying it with a real SQLite database while confirming unique email after signup. I can't find a signup/page.tsx'use client'
import React from 'react'
import { useFormState } from 'react-dom'
import Link from 'next/link'
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { parseWithZod } from '@conform-to/zod'
import { signup } from '@/app/lib/actions'
import { createSignupSchema, signupSchema } from '@/app/lib/zod-schema'
export default function SignupPage() {
const [lastResult, action] = useFormState(signup, undefined)
const [form, fields] = useForm({
id: 'signup-form',
lastResult,
onValidate({ formData }) {
return parseWithZod(formData, {
schema: (control) => createSignupSchema(control),
})
},
shouldValidate: 'onBlur',
})
return (
<>
<Link href="/" className="anchor-dark">
go home
</Link>
<form action={action} {...getFormProps(form)}>
<input
className={!fields.email.valid ? 'text-red-500' : ''}
{...getInputProps(fields.email, { type: 'email' })}
key={fields.email.key}
/>
<div className="text-red-500">{fields.email.errors}</div>
<button type="submit">signup</button>
</form>
</>
)
} lib/zod-schema.tsimport type { Intent } from '@conform-to/react'
import { conformZodMessage } from '@conform-to/zod'
import { z } from 'zod'
export const signupSchema = z.object({
email: z
.string({ required_error: 'Email is required' })
.email('Invalid email address'),
})
export function createSignupSchema(
intent: Intent | null,
options?: {
// isEmailUnique is only defined on the server
isEmailUnique: (email: string) => boolean
}
) {
return z.object({
email: z
.string({ required_error: 'email is required' })
.email('Invalid Email address')
// Pipe the schema so it runs only if the email is valid
.pipe(
z.string().superRefine(async (email, ctx) => {
const isValidatingEmail =
intent === null ||
(intent.type === 'validate' && intent.payload.name === 'email')
if (!isValidatingEmail) {
ctx.addIssue({
code: 'custom',
message: conformZodMessage.VALIDATION_SKIPPED,
})
return
}
if (typeof options?.isEmailUnique !== 'function') {
ctx.addIssue({
code: 'custom',
message: conformZodMessage.VALIDATION_UNDEFINED,
fatal: true,
})
return
}
const isUnique = options.isEmailUnique(email)
if (!isUnique) {
ctx.addIssue({
code: 'custom',
message: 'Email is already used',
})
return
}
})
),
})
} lib/actions.ts'use server'
import React from 'react'
import { useFormState, useFormStatus } from 'react-dom'
import Link from 'next/link'
import { Argon2id } from 'oslo/password'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { generateId } from 'lucia'
import { z } from 'zod'
import { countDistinct, eq } from 'drizzle-orm'
import { getInputProps, useForm } from '@conform-to/react'
import { parseWithZod } from '@conform-to/zod'
import { lucia } from '@/app/auth/lucia'
import { db } from '@/app/db/index'
import { userTable } from '@/app/db/schema'
import { createSignupSchema, signupSchema } from '@/app/lib/zod-schema'
export async function signup(prevState: unknown, formData: FormData) {
const userId = generateId(15)
console.log('signup')
const submission = await parseWithZod(formData, {
schema: (control) =>
// create a zod schema base on the control
createSignupSchema(control, {
isEmailUnique(email) {
const user = db
.select()
.from(userTable)
.where(eq(userTable.email, email))
.all()
return !user.length
},
}).transform(async (data, ctx) => {
console.log('parseWithZod')
const session = await db
.insert(userTable)
.values({ id: userId, ...data })
console.log('action')
console.log({ session })
if (!session) {
ctx.addIssue({
path: ['email'],
code: z.ZodIssueCode.custom,
message: 'Email already exists!',
})
return z.NEVER
}
return { ...data, session }
}),
async: true,
})
if (submission.status !== 'success') {
return submission.reply()
}
const session = await lucia.createSession(userId, {})
const sessionCookie = lucia.createSessionCookie(session.id)
cookies().set(
sessionCookie.name,
sessionCookie.value,
sessionCookie.attributes
)
return redirect('/')
} these are the only 3 files used. i have the code in sqlite branch. how can i make this error go away? i tried so many things by not making it |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 4 replies
-
Sorry that setting up an async validation with zod is indeed tricky. I probably should add a few more remarks on the examples. But here is what you need: export function createSignupSchema(
intent: Intent | null,
options?: {
// isEmailUnique is only defined on the server
isEmailUnique: (email: string) => boolean
}
) {
return z.object({
email: z
.string({ required_error: 'email is required' })
.email('Invalid Email address')
// Pipe the schema so it runs only if the email is valid
.pipe(
// NOTE: The function you passed to `.superRefine()` must not be `async`
// As we can only run the validation synchronously on the client side.
// Marking it `async` will turn the callback a promise and fail to run in sync
z.string().superRefine((email, ctx) => {
const isValidatingEmail =
intent === null ||
(intent.type === 'validate' && intent.payload.name === 'email')
if (!isValidatingEmail) {
ctx.addIssue({
code: 'custom',
message: conformZodMessage.VALIDATION_SKIPPED,
})
return
}
if (typeof options?.isEmailUnique !== 'function') {
ctx.addIssue({
code: 'custom',
message: conformZodMessage.VALIDATION_UNDEFINED,
fatal: true,
})
return
}
// By reaching here, we are sure this is in the server
// and we will run an async process by returning a promise
// as we can't use the async/await keyword here
return options.isEmailUnique(email).then(isUnique => {
if (!isUnique) {
ctx.addIssue({
code: 'custom',
message: 'Email is already used',
})
return
}
});
})
),
})
} The rest of the changes looks fine to me. |
Beta Was this translation helpful? Give feedback.
oh. If
isEmailUnique
is sync, that's fine. Then all you need is to remove theasync
keyword on the.superRefine()
as I mentioned above.