Security News
Cloudflare Adds Security.txt Setup Wizard
Cloudflare has launched a setup wizard allowing users to easily create and manage a security.txt file for vulnerability disclosure on their websites.
@conform-to/react
Advanced tools
By default, the browser calls the reportValidity() API on the form element when a submission is triggered. This checks the validity of all the fields and reports through the error bubbles.
This hook enhances the form validation behaviour by:
import { useForm } from '@conform-to/react';
function LoginForm() {
const [form, { email, password }] = useForm({
/**
* If the form id is provided, Id for label,
* input and error elements will be derived.
*/
id: undefined,
/**
* Define when the error should be reported initially.
* Support "onSubmit", "onChange", "onBlur".
*
* Default to `onSubmit`.
*/
initialReport: 'onBlur',
/**
* An object representing the initial value of the form.
*/
defaultValue: undefined;
/**
* An object describing the state from the last submission
*/
state: undefined;
/**
* An object describing the constraint of each field
*/
constraint: undefined;
/**
* Enable native validation before hydation.
*
* Default to `false`.
*/
fallbackNative: false,
/**
* Accept form submission regardless of the form validity.
*
* Default to `false`.
*/
noValidate: false,
/**
* A function to be called when the form should be (re)validated.
* Only sync validation is supported
*/
onValidate({ form, formData }) {
// ...
},
/**
* The submit event handler of the form.
*/
onSubmit(event, { formData, submission }) {
// ...
},
});
// ...
}
It is a group of properties required to hook into form events. They can also be set explicitly as shown below:
function RandomForm() {
const [form] = useForm();
return (
<form
ref={form.props.ref}
id={form.props.id}
onSubmit={form.props.onSubmit}
noValidate={form.props.noValidate}
>
{/* ... */}
</form>
);
}
Yes! It will fallback to native form submission as long as the submit event is not default prevented.
import { useFrom } from '@conform-to/react';
import { Form } from '@remix-run/react';
function LoginForm() {
const [form] = useForm();
return (
<Form method="post" action="/login" {...form.props}>
{/* ... */}
</Form>
);
}
This hook enables you to work with nested object by monitoring the state of each nested field and prepraing the config required.
import { useForm, useFieldset, conform } from '@conform-to/react';
interface Address {
street: string;
zipcode: string;
city: string;
country: string;
}
function Example() {
const [form, { address }] = useForm<{ address: Address }>();
const { city, zipcode, street, country } = useFieldset(
form.ref,
address.config,
);
return (
<form {...form.props}>
<fieldset>
<legned>Address</legend>
<input {...conform.input(street.config)} />
<div>{street.error}</div>
<input {...conform.input(zipcode.config)} />
<div>{zipcode.error}</div>
<input {...conform.input(city.config)} />
<div>{city.error}</div>
<input {...conform.input(country.config)} />
<div>{country.error}</div>
</fieldset>
<button>Submit</button>
</form>
);
}
If you don't have direct access to the form ref, you can also pass a fieldset ref.
import { type FieldConfig, useFieldset } from '@conform-to/react';
import { useRef } from 'react';
function Fieldset(config: FieldConfig<Address>) {
const ref = useRef<HTMLFieldsetElement>(null);
const { city, zipcode, street, country } = useFieldset(ref, config);
return <fieldset ref={ref}>{/* ... */}</fieldset>;
}
conform utilises the DOM as its context provider / input registry, which maintains a link between each input / button / fieldset with the form through the form property. The ref object allows it to restrict the scope to elements associated to the same form only.
function ExampleForm() {
const formRef = useRef();
const inputRef = useRef();
useEffect(() => {
// Both statements will log `true`
console.log(formRef.current === inputRef.current.form);
console.log(formRef.current.elements.namedItem('title') === inputRef.current)
}, []);
return (
<form ref={formRef}>
<input ref={inputRef} name="title">
</form>
);
}
This hook enables you to work with array and support list command button builder to modify a list. It can also be used with useFieldset for nested list at the same time.
import { useForm, useFieldList, list } from '@conform-to/react';
/**
* Consider the schema as follow:
*/
type Schema = {
items: string[];
};
function Example() {
const [form, { items }] = useForm<Schema>();
const list = useFieldList(form.ref, items.config);
return (
<fieldset ref={ref}>
{list.map((item, index) => (
<div key={item.key}>
{/* Setup an input per item */}
<input {...conform.input(item.config)} />
{/* Error of each item */}
<span>{item.error}</span>
{/* Setup a delete button (Note: It is `items` not `item`) */}
<button {...list.remove(items.config.name, { index })}>Delete</button>
</div>
))}
{/* Setup a button that can append a new row with optional default value */}
<button {...list.append(items.config.name, { defaultValue: '' })}>
add
</button>
</fieldset>
);
}
It returns a ref object and a set of helpers that dispatch corresponding dom event.
import { useForm, useInputEvent } from '@conform-to/react';
import { Select, MenuItem } from '@mui/material';
import { useState, useRef } from 'react';
function MuiForm() {
const [form, { category }] = useForm();
const [value, setValue] = useState(category.config.defaultValue ?? '');
const [ref, control] = useInputEvent({
onReset: () => setValue(category.config.defaultValue ?? ''),
});
const inputRef = useRef<HTMLInputElement>(null);
return (
<form {...form.props}>
{/* Render a shadow input somewhere */}
<input
ref={ref}
{...conform.input(category.config, { hidden: true })}
onChange={(e) => setValue(e.target.value)}
onFocus={() => inputRef.current?.focus()}
/>
{/* MUI Select is a controlled component */}
<TextField
label="Category"
inputRef={inputRef}
value={value}
onChange={control.change}
onBlur={control.blur}
select
>
<MenuItem value="">Please select</MenuItem>
<MenuItem value="a">Category A</MenuItem>
<MenuItem value="b">Category B</MenuItem>
<MenuItem value="c">Category C</MenuItem>
</TextField>
</form>
);
}
It provides several helpers to remove the boilerplate when configuring a form control.
You are recommended to create a wrapper on top if you need to integrate with custom input component. As the helper derives attributes for accessibility concerns and helps focus management.
Before:
import { useForm } from '@conform-to/react';
function Example() {
const [form, { title, description, category }] = useForm();
return (
<form {...form.props}>
<input
type="text"
name={title.config.name}
form={title.config.form}
defaultValue={title.config.defaultValue}
requried={title.config.required}
minLength={title.config.minLength}
maxLength={title.config.maxLength}
min={title.config.min}
max={title.config.max}
multiple={title.config.multiple}
pattern={title.config.pattern}
/>
<textarea
name={description.config.name}
form={description.config.form}
defaultValue={description.config.defaultValue}
requried={description.config.required}
minLength={description.config.minLength}
maxLength={description.config.maxLength}
/>
<select
name={category.config.name}
form={category.config.form}
defaultValue={category.config.defaultValue}
requried={category.config.required}
multiple={category.config.multiple}
>
{/* ... */}
</select>
</form>
);
}
After:
import { useForm, conform } from '@conform-to/react';
function Example() {
const [form, { title, description, category }] = useForm();
return (
<form {...form.props}>
<input {...conform.input(title.config, { type: 'text' })} />
<textarea {...conform.textarea(description.config)} />
<select {...conform.select(category.config)}>{/* ... */}</select>
</form>
);
}
It provides serveral helpers to configure a command button for modifying a list.
import { list } from '@conform-to/react';
function Example() {
return (
<form>
{/* To append a new row with optional defaultValue */}
<button {...list.append('name', { defaultValue })}>Append</button>
{/* To prepend a new row with optional defaultValue */}
<button {...list.prepend('name', { defaultValue })}>Prepend</button>
{/* To remove a row by index */}
<button {...list.remove('name', { index })}>Remove</button>
{/* To replace a row with another defaultValue */}
<button {...list.replace('name', { index, defaultValue })}>
Replace
</button>
{/* To reorder a particular row to an another index */}
<button {...list.reorder('name', { from, to })}>Reorder</button>
</form>
);
}
It returns the properties required to configure a command button for validation.
import { validate } from '@conform-to/react';
function Example() {
return (
<form>
{/* To validate a single field by name */}
<button {...validate('email')}>Validate email</button>
{/* To validate the whole form */}
<button {...validate()}>Validate</button>
</form>
);
}
It lets you trigger an intent without requiring users to click on a button. It supports both list and validate intent.
import {
useForm,
useFieldList,
conform,
list,
requestIntent,
} from '@conform-to/react';
import DragAndDrop from 'awesome-dnd-example';
export default function Todos() {
const [form, { tasks }] = useForm();
const taskList = useFieldList(form.ref, tasks.config);
const handleDrop = (from, to) =>
requestIntent(form.ref.current, list.reorder({ from, to }));
return (
<form {...form.props}>
<DragAndDrop onDrop={handleDrop}>
{taskList.map((task, index) => (
<div key={task.key}>
<input {...conform.input(task.config)} />
</div>
))}
</DragAndDrop>
<button>Save</button>
</form>
);
}
It returns all input / select / textarea or button in the forms. Useful when looping through the form elements to validate each field manually.
import { useForm, parse, getFormElements } from '@conform-to/react';
export default function LoginForm() {
const [form] = useForm({
onValidate({ form, formData }) {
const submission = parse(formData);
for (const element of getFormElements(form)) {
switch (element.name) {
case 'email': {
if (element.validity.valueMissing) {
submission.error.push([element.name, 'Email is required']);
} else if (element.validity.typeMismatch) {
submission.error.push([element.name, 'Email is invalid']);
}
break;
}
case 'password': {
if (element.validity.valueMissing) {
submission.error.push([element.name, 'Password is required']);
}
break;
}
}
}
return submission;
},
// ....
});
// ...
}
It parses the formData based on the naming convention.
import { parse } from '@conform-to/react';
const formData = new FormData();
const submission = parse(formData);
console.log(submission);
This helper checks if the scope of validation includes a specific field by checking the submission:
import { shouldValidate } from '@conform-to/react';
/**
* The submission intent give us hint on what should be valdiated.
* If the intent is 'validate/:field', only the field with name matching must be validated.
* If the intent is undefined, everything should be validated (Default submission)
*/
const intent = 'validate/email';
// This will log 'true'
console.log(shouldValidate(intent, 'email'));
// This will log 'false'
console.log(shouldValidate(intent, 'password'));
FAQs
Conform view adapter for react
The npm package @conform-to/react receives a total of 69,092 weekly downloads. As such, @conform-to/react popularity was classified as popular.
We found that @conform-to/react demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Cloudflare has launched a setup wizard allowing users to easily create and manage a security.txt file for vulnerability disclosure on their websites.
Security News
The Socket Research team breaks down a malicious npm package targeting the legitimate DOMPurify library. It uses obfuscated code to hide that it is exfiltrating browser and crypto wallet data.
Security News
ENISA’s 2024 report highlights the EU’s top cybersecurity threats, including rising DDoS attacks, ransomware, supply chain vulnerabilities, and weaponized AI.