Now that we have our components, making a form is easy!
import {DataFunctionArgs,json,redirect}from"@remix-run/node";import{useLoaderData}from"@remix-run/react";import*asyupfrom"yup";import{validationError,ValidatedForm,withYup}from"remix-validated-form";import{MyInput,MySubmitButton}from"~/components/Input";// Using yup in this example, but you can use anythingconstvalidator=withYup(yup.object({firstName: yup.string().label("First Name").required(),lastName: yup.string().label("Last Name").required(),email: yup.string().email().label("Email").required(),}));exportconstaction=async({ request }: DataFunctionArgs)=>{constfieldValues=awaitvalidator.validate(awaitrequest.formData());if(fieldValues.error)returnvalidationError(fieldValues.error);const{ firstName, lastName, email }=fieldValues.data;// Do something with correctly typed values;returnredirect("/");};exportconstloader=async(args: DataFunctionArgs)=>{returnjson({defaultValues: {firstName: "Jane",lastName: "Doe",email: "jane.doe@example.com",},});};exportdefaultfunctionMyForm(){const{ defaultValues }=useLoaderData<typeofloader>();return(<ValidatedFormvalidator={validator}method="post"defaultValues={defaultValues}><MyInputname="firstName"label="First Name"/><MyInputname="lastName"label="Last Name"/><MyInputname="email"label="Email"/><MySubmitButton/></ValidatedForm>);}
Nested objects and arrays
You can use nested objects and arrays by using a period (.) or brackets ([]) for the field names.
In order to make an adapter for your validation library of choice,
you can create a function that accepts a schema from the validation library and turns it into a validator.
Note the use of createValidator.
It takes care of unflattening the data for nested objects and arrays
since the form doesn’t know anything about object and arrays and this should be handled by the adapter.
For more on this you can check the implementations for withZod and withYup.
The out-of-the-box support for yup in this library works like this:
export constwithYup=<SchemaextendsAnyObjectSchema>(validationSchema: Schema// For best result with Typescript, we should type the `Validator` we return based on the provided schema): Validator<InferType<Schema>>=>createValidator({validate: (unvalidatedData)=>{// Validate with yup and return the validated & typed data or the errorif(isValid)return{data: {field1: "someValue"},error: undefined};elsereturn{error: {field1: "Some error!"},data: undefined};},validateField: (unvalidatedData,field)=>{// Validate the specific field with yupif(isValid)return{error: undefined};elsereturn{error: "Some error"};},});
Frequenty Asked Questions
Why are my fields triggering the native HTML validations before remix-validated-form ones?
This is happening because you or the library you are using is passing the required attribute to the fields.
This library doesn’t take care of eliminating them and it’s up to the user how they want to manage the validation errors.
If you wan’t to disable all native HTML validations you can add noValidate to <ValidatedForm>.
We recommend this approach since the validation will still work even if JS is disabled.
How do we trigger toast messages on success?
Problem: how do we trigger a toast message on success if the action redirects away from the form route? The Remix solution is to flash a message in the session and pick this up in a loader function, probably in root.tsx
See the Remix documentation for more information.
Why is my cancel button triggering form submission?
Problem: the cancel button has an onClick handler to navigate away from the form route but instead it is submitting the form.
A button defaults to type="submit" in a form which will submit the form by default. If you want to prevent this you can add type="reset" or type="button" to the cancel button.