Skip to main content

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:

0
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>
);
}
info

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']).
info

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:

0
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>
);
}
note

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:

0
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 the validators configuration (here sum).
  • 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:

0
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>
);
}