Skip to main content

Local fields

Local validation

When your form is complex you might want to split you form into multiple components.

In that case it might be preferable for you to declare the validator with the field and not on the form level.

In such scenario you can use the useInput hook to achieve this goal.

Just pass the name of the field and and the validator as parameters of the useInput hook.

0
import type { FormEvent } from 'react';
import type { IFormValues } from '@per-form/react';
import type { IProps } from '../types';
import { FormProvider, useForm, useInput } from '@per-form/react';

const validator = (values: IFormValues) =>
String(values.text).includes('foo') ? '' : 'Value does not include "foo"';

function Input() {
const { errors } = useInput({ id: 'toto', name: 'text', validator });
return (
<>
<input name="text" required />
{errors.all.text && <div className="error">{errors.all.text}</div>}
</>
);
}

export default function Demo(props: IProps) {
function handleSubmit(_e: FormEvent<HTMLFormElement>, values: IFormValues) {
console.log(values);
}

const { formProps, ...context } = useForm({
...props,
onSubmit: handleSubmit,
});

return (
<FormProvider {...context}>
<form {...formProps}>
<Input />
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
warning

With the hook version you will have to add yourself the @per-form/react <FormProvider> component !

info

By default, for performance reason, local errors won't be present in the errors object at the form level.

In the demo we use filterLocalErrors=false on useForm and <Form> to be able to display the error (when you use filterLocalErrors=false error will also show up at the form level).

This is also the reason why the render counter still increments on local fields demos.

note

The local validator is not present in the global property of the error object at the form level.

Only global validators (validators set up at the form level) will show up in the global object.

With default filterLocalErrors=true, if you have a global validator targeting a local field, then the global validator error will be :

  • present in the global property and not present in the validator property at the form level
  • not present in the global property but present in the validator property at the field level

Local cross field validation

You can mix local validation and cross field validation using the useInputs hook.

0
import type { FormEvent } from 'react';
import type { IFormValues } from '@per-form/react';
import type { IProps } from '../types';
import { FormProvider, useForm, useInputs } from '@per-form/react';

const validator = (values: IFormValues) =>
Number(values.a) + Number(values.b) === 42
? ''
: 'The sum must be equal to 42';
const names = ['a', 'b'];

function Inputs() {
const { errors } = useInputs({ names, validators: validator });
return (
<>
<input name="a" required type="number" />
<input name="b" required type="number" />
{errors.validator['a,b']?.error && (
<div className="error">{errors.validator['a,b'].error}</div>
)}
</>
);
}

export default function Demo(props: IProps) {
function handleSubmit(_e: FormEvent<HTMLFormElement>, values: IFormValues) {
console.log(values);
}

const { formProps, ...context } = useForm({
...props,
onSubmit: handleSubmit,
});

return (
<FormProvider {...context}>
<form {...formProps}>
<Inputs />
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
tip

In that case the system will define an id for the validator based on the names (here 'a,b').

But you can also pass an object for the validators property where key are ids and values are validators:

useInputs({ names, validators: { sum: validator } })

Or simply use the id property on useInputs (it only works if you provide one validator):

useInputs({ id: 'sum', names, validators: validator })

Default values and transformers

You can also provide default values and transformers for local fields:

0
import type { FormEvent } from 'react';
import type { IFormValues } from '@per-form/react';
import type { IProps } from '../types';
import { FormProvider, useForm, useInput } from '@per-form/react';

function Input() {
const { errors } = useInput({
defaultValue: 0,
name: 'count',
transformer: Number,
});
return (
<>
<input name="count" required type="number" />
{errors.all.count && <div className="error">{errors.all.count}</div>}
</>
);
}

export default function Demo(props: IProps) {
function handleSubmit(_e: FormEvent<HTMLFormElement>, values: IFormValues) {
console.log(values);
}

const { formProps, ...context } = useForm({
...props,
onSubmit: handleSubmit,
});

return (
<FormProvider {...context}>
<form {...formProps}>
<Input />
<div className="actions">
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</div>
</form>
</FormProvider>
);
}