Validation
Custom validation
To add some validation on a field just use the validators
parameter and set a function for the field (the key must match the name) that either return an error string or empty string if there is no error:
import type { FormEvent } from 'react';
import type { IProps } from '../types';
import { type IFormValues, useForm } from '@per-form/react';
const validators = {
text: (values: IFormValues) =>
String(values.text).includes('foo') ? '' : 'Value does not include "foo"',
};
export default function Demo({ useNativeValidation }: IProps) {
function handleSubmit(_e: FormEvent<HTMLFormElement>, values: IFormValues) {
console.log(values);
}
const { errors, formProps } = useForm({
onSubmit: handleSubmit,
useNativeValidation,
validators,
});
return (
<form {...formProps}>
<input name="text" required />
{errors.all.text && <div className="error">{errors.all.text}</div>}
<button type="submit">Submit</button>
</form>
);
}
If possible declare the validators outside the component to avoid creating a new reference for each new render.
Each validator will receive two parameters:
- the filtered form
values
(You will only get the values of the field the validator is attached to) - the
names
array associated with the validator (in this case it will be['text']
).
With this syntax the key used in the error object will be the name of the field.
If you need to do a validation based on multiple field values check the next section.
Async validation
Validator can also return a promise containing the error string:
import type { FormEvent } from 'react';
import type { IProps } from '../types';
import { type IFormValues, useForm } from '@per-form/react';
import { delay } from '../time';
const validators = {
text: (values: IFormValues) =>
delay(
String(values.text).includes('foo') ? '' : 'Value does not include "foo"',
),
};
export default function Demo({ useNativeValidation }: IProps) {
function handleSubmit(_e: FormEvent<HTMLFormElement>, values: IFormValues) {
console.log(values);
}
const { errors, formProps } = useForm({
onSubmit: handleSubmit,
useNativeValidation,
validators,
});
return (
<form {...formProps}>
<input name="text" required />
{errors.all.text && <div className="error">{errors.all.text}</div>}
<button type="submit">Submit</button>
</form>
);
}
In this example delay
is just a function that return a Promise that will be resolved after 1s.
Cross field validation
Cross field validation enable the possibility of having a validator applied on multiples fields at the same time.
Declare the validator using an object with a names
property listing all related fields and set the validator function in the validator
property:
import type { FormEvent } from 'react';
import type { IProps } from '../types';
import { type IFormValues, useForm } from '@per-form/react';
const validators = {
sum: {
names: ['a', 'b'],
validator: (values: IFormValues) =>
Number(values.a) + Number(values.b) === 42
? ''
: 'The sum must be equal to 42',
},
};
export default function Demo({ useNativeValidation }: IProps) {
function handleSubmit(_e: FormEvent<HTMLFormElement>, values: IFormValues) {
console.log(values);
}
const { errors, formProps } = useForm({
onSubmit: handleSubmit,
useNativeValidation,
validators,
});
return (
<form {...formProps}>
<input name="a" required type="number" />
<input name="b" required type="number" />
{errors.validator.sum?.error && (
<div className="error">{errors.validator.sum.error}</div>
)}
<button type="submit">Submit</button>
</form>
);
}
In that case, when the validator does not pass:
- all field in the
all
property of the error object will contain the error. - the
validator
property of the error object will contain the error using the same id used in thevalidators
configuration (heresum
). - and the validator error will also be available in the
global
property of the error object.
Dynamic field validation
If you need to work with dynamic form, just update the names the validator applies on:
import type { IProps } from '../types';
import { type FormEvent, useRef, useState } from 'react';
import { type IFormValues, useForm } from '@per-form/react';
function validator(values: IFormValues) {
return Object.values(values).reduce((a, b) => Number(a) + Number(b), 0) === 42
? ''
: 'The sum must be equal to 42';
}
export default function Demo({ useNativeValidation }: IProps) {
const [names, setNames] = useState<string[]>([]);
const ref = useRef(0);
function handleAdd() {
setNames(names.concat(`dynamic-${ref.current++}`));
}
function handleSubmit(_e: FormEvent<HTMLFormElement>, values: IFormValues) {
console.log(values);
}
const { errors, formProps } = useForm({
onSubmit: handleSubmit,
useNativeValidation,
validators: { sum: { names, validator } },
});
return (
<form {...formProps}>
<button onClick={handleAdd} type="button">
Add input
</button>
{names.map((name) => (
<input key={name} name={name} required type="number" />
))}
{errors.main && <div className="error">{errors.main.error}</div>}
<button type="submit">Submit</button>
</form>
);
}