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