RVF (Remix Validated Form)
repository·main·Indexed 21 days ago
https://github.com/airjp73/rvfA library for form validation and state management in React, designed for progressive enhancement and compatibility with native form APIs. It supports server-side rendering frameworks like Remix and Next.js through specialized adapters such as @rvf/react-router and @rvf/react, and provides schema validation integration via @rvf/zod, @rvf/yup, and @rvf/valibot.
What's inside RVF
- RVF is a library designed for easy form validation and state management in React applications. It is built to be both progressively enhanced and scalable for complex form requirements.
What is RVF Core?
mainRVF Core is the central implementation engine for the RVF ecosystem. It provides the underlying logic used by various adapters (such as@rvf/react,@rvf/react-router, and schema validators like@rvf/zod) to handle form validation and state.Use the RVF Zod adapter
mainThe@rvf/zodpackage is an adapter that allows you to use Zod schemas for form validation within the RVF (Remix Validated Form) ecosystem. This enables seamless integration between Zod's schema validation and RVF's progressive enhancement and form management capabilities.Use RVF set-get for deeply nested data
mainThe@rvf/set-getpackage provides internal utilities and types designed for working with deeply nested data structures. While primarily used by the RVF ecosystem, the API is considered stable and can be used in external projects for managing nested object access and updates.Use the RVF Yup adapter
mainThe@rvf/yuppackage is a Yup adapter for RVF. It allows you to use Yup schemas for form validation within the RVF ecosystem.Use the RVF Valibot adapter
mainThe@rvf/valibotpackage is an adapter that allows you to use Valibot for schema validation within the RVF (Remix Validated Form) ecosystem. This enables progressive enhancement and powerful form validation using Valibot schemas.Common traits of RVF input types
mainRVF validates and submits data directly from the HTML
formelement by default, supporting all native input types.Types
Empty inputs are generally represented as
nullin cases where the main type of the input is not astring.Setting default values
- All input types (except
file) can have their default value set using astring. - For non-string types (like
number), you can typically set the default value using that specific type.
Observing and setting values
The type returned by
form.value(fieldName)is always the same as the type passed intodefaultValues. You should use the same type when callingform.setValue(fieldName, value).Validating
Unless you are using state mode, the data received by your schema will always be a
stringor astring[]:- If only one input exists for a field, the value is a
string. - If multiple inputs share the same name, the value is a
string[].
- All input types (except
Type safety for defaultValues
mainWhen using a modern Standard Schema validator, the type of
defaultValuesis automatically inferred from your schema. The resultingformobject methods (such asform.value()andform.setValue()) will use this inferred input type.Note: If you are using a legacy
validator, the type ofdefaultValuesis not inferred from the validator; instead, it is inferred solely from thedefaultValuesobject itself.const form = useForm({ schema: z.object({ name: z.string(), }), // Type inferred from schema above! defaultValues: { name: "John Doe", }, });Use `FieldApi` to interact with form fields
mainThe
FieldApiobject provides a set of helper methods and properties to manage the state, validation, and DOM properties of a specific field within an RVF form. It allows you to handle values, errors, touched states, and DOM attributes likerefandnameeasily.// Example of accessing FieldApi properties console.log(field.name); console.log(field.value); console.log(field.error); console.log(field.touched);Use `FieldArrayApi` to manage field arrays
mainThe
FieldArrayApiprovides a set of helper methods for interacting with and manipulating arrays of fields within a form. It allows you to add, remove, move, and iterate over items in an array while providing scopedFormApiinstances for each individual item.// Example of using the map method to render array items myArray.map((key, item, index) => ( <div key={key}> {item.value("name")} <button type="button" onClick={() => { myArray.remove(index); }} > Delete </button> </div> ));Handle nested objects using dot notation
mainRVF handles nested object structures by treating field names as paths using standard JavaScript dot notation. When naming your
<input />elements, use thenameattribute to specify the path to the property within the resulting data object.const inputs = ( <> <input name="todo.title" /> <input name="todo.description" /> </> ) // The resulting data object will look like: const result = { todo: { title: "Take out the trash", description: "I should really do this", }, }Use `scope` to create subforms and nested abstractions
mainRVF allows you to scope a form down to a specific part of your data structure using the
scopemethod on theFormApiobject. This is useful for creating reusable components that represent subforms or individual fields without needing to know the full shape of the parent form.When you call
scope(fieldName), it returns aFormScopeobject. You can chain multiplescopecalls to navigate deeply nested or recursive data structures.To use a
FormScopein a component, you must pass it to one of the following hooks:useFormScopeuseFielduseFieldArray
const form = useForm({ defaultValues: { foo: "foo", bar: { baz: "baz" } }, // ...etc }); // Scoping to a top-level field const fooScope = form.scope("foo"); // fooScope type: FormScope<string> // Scoping to a nested field const barScope = form.scope("bar"); // barScope type: FormScope<{ baz: string }> // Chaining scope for deep nesting const bazScope = barScope.scope("baz"); // bazScope type: FormScope<string>