Implement Async Server-Side Validation
Say that we're developing a form for a company that needs to register users, but they want to limit registrations to certain zip codes.
We want to ensure that the zip code is valid and that we handle it appropriately, which might require using microservices or other complex processes.
As a stand-in for a more complicated process, we'll say that we only handle zip codes that start with '9'.
Building a Zip Code Validator
To start, we'll create a new file at app/validateZipCode.ts for our server action.
Inside of this file, we'll export an async validateZipCode function that accepts a zipcode string and returns a Promise with a Boolean value. Inside the function, we'll log the zipcode we're trying to check and return true if the zipcode is valid and starts with '9'. Since this is a server action, we'll add "use server" at the top of the file:
"use server";
export async function validateZipCode(zipCode: string): Promise<boolean> {
console.log("validateZipcode on SERVER", zipCode)
return /^\d{5}/.test(zipCode) && zipCode.startsWith('9');
}
In a real-world application, you could perform all sorts of asynchronous processing within this function, including hitting various microservices, but we'll just stick with this.
Update the Schema
Over in src/app/registrationSchema.tsx, import validateZipcode:
import { validateZipCode } from "./validateZipCode";
Then, add a zipCode field to the schema and use the refine method to run the validateZipCode function. If there's an error, return the message "Invalid zipcode":
export const schema = z.object({
// rest of the schema as before
zipCode: z.string().refine(validateZipCode, {
message: "Invalid zipcode",
}),
});
Update the Page
Inside of the page.tsx file, we'll add a FormField for the zipcode:
<FormField
control={form.control}
name="zipcode"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="" {...field} />
</FormControl>
<FormDescription>Your zipcode (NNNNN).</FormDescription>
<FormMessage />
</FormItem>
)}
/>
Now when we attempt to submit the form, the console will display our validateZipcode on SERVER message with the number we put in. When we put in invalid numbers, we see the error message as expected.
However, if we then input a valid zip code and hit submit, we encounter an internal server error.
Fixing the Internal Server Error for Valid Zip Codes
The problem arises from the fact that we've added the async validateZipCode to our registrationSchema, but the safeParse function in RegistrationForm.tsx is running synchronously. Trying to run async functions in synchronous mode won't work.
Instead, we need to update the parsed variable to use the safeParseAsync function instead:
export default function Home() {
const onFormAction = async (issues?: string[], formData: FormData) => {
"use server";
const data = Object.fromEntries(formData);
const parsed = await schema.safeParseAsync(data);
};
}
Upon making these changes, the form accepts valid zip codes that begin with 9 and successfully registers users. The asynchronous validation of zip codes completes without a hitch!
Now you know how to create custom server-side validations using Next.js server actions!