Skip to main content

Messages and i18n

Custom error messages

You can send the error message you want with validators or manual errors but the browser is responsible for native errors.

The good thing is that they are already translated in the user language but sometimes you want to customize those error or just want the translation them to match the language chosen in your website.

In that case you can customize error messages using the messages parameter:

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

const messages = { valueMissing: 'Did you miss something ?' };

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

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

return (
<form {...formProps}>
<input name="text" required />
{errors.all.text && <div className="error">{errors.all.text}</div>}
<button type="submit">Submit</button>
</form>
);
}

Error keys

When using the messages parameter you will have to use the following keys in your object:

  1. For validators, use the same string as the ones returned from your validators.
  2. Same for manual errors.
  3. For native errors, use ValidityState keys:
    • badInput: if the user has provided input that the browser is unable to convert.
    • patternMismatch: if the value does not match the specified pattern attribute.
    • rangeOverflow: if the value is greater than the maximum specified by the max attribute.
    • rangeUnderflow: if the value is less than the minimum specified by the min attribute.
    • stepMismatch: if the value does not fit the rules determined by the step attribute.
    • tooLong: if the value exceeds the specified maxlength attribute.
    • tooShort: if the value fails to meet the specified minlength attribute.
    • typeMismatch: if the value is not in the required syntax (when type is email or url).
    • valueMissing: if the element has a required attribute.
0
import { DatePicker } from '@mui/x-date-pickers';
import dayjs, { type Dayjs } from 'dayjs';
import { type FormEvent, useState } from 'react';
import type { IProps } from '../types';
import { type IFormValues, useForm } from '@per-form/react';

const defaultValues = { mui: null };
const validators = {
mui: (values: IFormValues) => {
const date = values.mui as Dayjs;
return date?.date() > 15 ? '' : 'dateUnderflow';
},
};

const messages = {
dateUnderflow: 'Choose a date after the 15th.',
minDate: 'The date must be greater than or equal to today.',
valueMissing: 'Did you miss something ?',
};

export default function Demo(props: IProps) {
const [value, setValue] = useState<Dayjs | null>(null);

function handleReset() {
setValue(null);
}

function handleSubmit(_e: FormEvent<HTMLFormElement>, values: IFormValues) {
console.log(values);
}

const { errors, formProps, onChange, onError } = useForm({
...props,
defaultValues,
messages,
onChangeOptOut: 'mui',
onReset: handleReset,
onSubmit: handleSubmit,
validators,
});

return (
<form {...formProps}>
<DatePicker
minDate={dayjs()}
name="mui"
onChange={onChange(setValue, { name: 'mui' })}
onError={onError('mui')}
slotProps={{ textField: { required: true } }}
value={value}
/>
{errors.all.mui && <div className="error">{errors.all.mui}</div>}
<div className="actions">
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</div>
</form>
);
}
tip

By using this technique you centralize all translations in one place which is more convenient for i18n.

Local custom messages

You can also provide local custom messages using the useInput or useInputs hooks:

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') ? '' : 'fooError';

const globalMessages = { valueMissing: 'did you miss something ?' };
const localMessages = { fooError: 'Value does not include "foo"' };

function Input() {
const { errors } = useInput({
messages: localMessages,
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,
messages: globalMessages,
onSubmit: handleSubmit,
});

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

Global messages are also available for locally declared fields, but local messages will only apply to associated fields.

If global and local keys conflict, local messages will take precedence over global messages.