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!