Reputation: 153
I have this piece of a code. I want to add error messages depending on user's locale, but yup throws errors, same if fields are filled in incorrectly
[missing "en.login.emailRequiredError" translation] [missing "en.login.passRequiredError" translation]
const schema = yup.object().shape({
email: yup
.string()
.email(i18n.t('login.emailSpellError'))
.required(i18n.t('login.emailRequiredError')),
password: yup
.string()
.matches(/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,15})$/i, i18n.t('login.passSpellError'))
.required(i18n.t('login.passRequiredError')),
});
i18n.t('login.passRequiredError') works fine when I put it into a render method for checking it but it does not work with the yup. Any suggestions? Thanks in advance
Upvotes: 13
Views: 24397
Reputation: 51
Adding on @Ilya Pasternak answer, for the react hook form users I've created a fully typed hook that will simplify and encapsulate the logic:
export const useReactiveTranslatedForm = <T extends AnyObject>(
getSchemaFunction: () => ObjectSchema<T>,
defaultValues: DefaultValues<T>
) => {
const { i18n } = useTranslation()
const memoizedSchema = useMemo(() => getSchemaFunction(), [i18n.language])
const form = useForm<T>({
resolver: yupResolver(memoizedSchema),
defaultValues
})
useEffect(() => {
if (form.formState.isSubmitted) {
form.trigger()
}
}, [i18n.language])
return form
}
Upvotes: 1
Reputation: 21
I encountered this problem when using react-hook-form, I managed to solve the problem by adding useEffect to the form processing hook
export type LoginFormType = {
captcha: string
email: string
password: string
}
export const getLoginFormSchema = (): ObjectSchema<LoginFormType> =>
yup.object({
captcha: yup.string().required(i18n.t('Enter code from image')).trim(),
email: yup
.string()
.required(i18n.t('Enter your email'))
.matches(emailRegex, i18n.t('Enter a valid email address'))
.trim(),
password: yup.string().required(i18n.t('Enter the password')).trim(),
})
export const defaultValues: LoginFormType = {
captcha: '',
email: '',
password: '',
}
export const useLoginForm = (defValue?: Partial<LoginFormType>): UseFormReturn<LoginFormType> => {
const schema = useMemo(() => getLoginFormSchema(), [i18n.language])
const form = useForm({
defaultValues: {
...defaultValues,
...defValue,
},
mode: 'onSubmit',
resolver: yupResolver(schema),
})
useEffect(() => {
if (form.formState.isSubmitted) {
form.trigger()
}
}, [i18n.language])
return form
}
Upvotes: 1
Reputation: 21
I've created a few custom hooks for this approach
This one to refresh error messages inside schema when is changing app language
import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router';
import { useMemo } from 'react';
const useSchema = (getSchema) => {
const { locale } = useRouter();
const resolver = useMemo(getSchema, [locale]);
return yupResolver(resolver);
};
export default useSchema;
And this one to set global in App component localised error messages
import { useTranslation } from 'react-i18next';
import { setLocale } from 'yup';
export const useLocalisedYupSchema = () => {
const { t } = useTranslation('common');
setLocale({
mixed: {
required: t('validation.required')
},
string: {
min: ({ min }) => t('validation.min', { min }),
max: ({ max }) => t('validation.max', { max })
},
});
};
Also usage of schemas inside component with React Hook Form
import { getChangePasswordSchema } from 'static/schemas/changePassword';
import useSchema from 'utils/hooks/useSchema';
import { useForm } from 'react-hook-form';
const AccountContentSecurity = () => {
...
const resolver = useSchema(getChangePasswordSchema);
const { reset, control, handleSubmit } = useForm({
defaultValues: {
'current_password': '',
'new_password': '',
'password_confirmation': '',
},
resolver,
});
...
and schema
import { passwordSchema } from 'static/schemas';
import { object } from 'yup';
export const getChangePasswordSchema = () => object({
'current_password': passwordSchema,
'new_password': passwordSchema,
'password_confirmation': passwordSchema,
});
Upvotes: 1
Reputation: 191
A solution will be to make a function that returns your validation schema. Then call that function in your component with the result memoized. This way, you are guaranteed that translations for validation messages are computed on the fly.
Another advantage here is you translate at the source of the message.
// Translation file
{
"validation.invalid-email": "Email is invalid",
"validation.field-required": "Field is required"
}
// Validation schema
const forgotPasswordSchema = () => {
return yup.object().shape({
email: yup
.string()
.email(i18n.t('validation.invalid-email'))
.required(i18n.t('validation.field-required')),
});
};
// Your component
const FormComponent = () => {
const schema = useMemo(() => forgotPasswordSchema(), [i18n.language]); // NB: `[i18n.language]` is optional and `[]` will suffice depending on how you're handling language change
return <>...</>;
}
Upvotes: 5
Reputation: 556
Yup Validation method,
// You define the key mentioned in the translation file, in my example 'Invalid email' and 'Required'
let ForgotPasswordSchema = yup.object().shape({
email: yup.string().email('Invalid email').required('Required'),
});
In render method,
// As per your definition
isInvalid={(!!errors.email) && this.context.t(!!errors.email)}
invalidText={(errors.email) && this.context.t(errors.email)}
Translation File
export const translations = {
"cy": {
"Required":"Gofynnol",
"Invalid email":"Nid yw'r cyfeiriad ebost yn ddilys",
}
};
Upvotes: 7
Reputation: 211
In your schema, replace:
.email(i18n.t('login.emailSpellError'))
with
.email('login.emailSpellError')
then in your render method:
{t(`form.errors.${form.errors.email}`)}
This assumes your translation file has an entry like this:
"form": { "errors": {"login": {"emailSpellError": "Your email is invalid"}}}}
The goal here is to move the t() method into your render method and have all translations happen there.
Upvotes: 21