在 Formik 表单中显示 Yup 验证错误后动态更改 (i18n) UI 语言,使用挂钩 -> 意外行为
Dynamically changing (i18n) UI language after Yup validation errors show in Formik form, using hooks -> unexpected behaviour
我正在向用户显示登录屏幕。这个登录屏幕(或整个应用程序,公平地说)可以用不同的语言显示。
除验证错误外,所有 UI 组件的语言切换工作正常。
目前,如果用户将电子邮件/密码字段留空,或输入无效的电子邮件地址,收到错误消息然后切换语言,错误消息不会更改为新语言。
经过大量猜测和实验后,我发现在单击 'de' 或 'en' 按钮两次,然后在输入中输入内容后,语言发生了变化。
我一辈子都想不出 useState 和 useEffect 挂钩的正确组合,使验证消息在按下任一语言按钮后立即显示 运行。
以下代码:
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Formik, Field, Form, ErrorMessage } from 'formik'
import * as Yup from 'yup'
import { useTranslation } from 'react-i18next'
import { accountService, alertService } from '@/_services'
function Login({ history, location }) {
//'common' is defined in i18n.js under init's namespace ('ns:') option
//if that's not set, use useTranslation('common'), or whatever the name
//of the json files you use in the language-specific folders is
const { t, i18n } = useTranslation()
const [emailRequired, setEmailRequired] = useState(t('login.email_error_required'))
const [emailInvalid, setEmailInvalid] = useState(t('login.email_error_invalid'))
const [passwordRequired, setPasswordRequired] = useState(t('login.password_error_required'))
const [validationSchema, setvalidationSchema] = useState(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
useEffect(() => {})
const initialValues = {
email: '',
password: '',
}
const onSubmit = ({ email, password }, { setSubmitting }) => {
alertService.clear()
accountService
.login(email, password)
.then(() => {
const { from } = location.state || { from: { pathname: '/' } }
history.push(from)
})
.catch((error) => {
setSubmitting(false)
alertService.error(error)
})
}
return (
<Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={onSubmit}>
{({ errors, touched, isSubmitting }) => (
<Form>
<h3 className="card-header">{t('login.page_title')}</h3>
<div className="card-body">
<div className="form-group">
<label>{t('login.email_input_label')}</label>
<Field name="email" type="text" className={'form-control' + (errors.email && touched.email ? ' is-invalid' : '')} />
<ErrorMessage name="email" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label>{t('login.password_input_label')}</label>
<Field name="password" type="password" className={'form-control' + (errors.password && touched.password ? ' is-invalid' : '')} />
<ErrorMessage name="password" component="div" className="invalid-feedback" />
</div>
<div className="form-row">
<div className="form-group col">
<button type="submit" disabled={isSubmitting} className="btn btn-primary">
{isSubmitting && <span className="spinner-border spinner-border-sm mr-1"></span>}
{t('login.login_btn_label')}
</button>
<Link to="register" className="btn btn-link">
{t('login.register_btn_label')}
</Link>
<button
type="button"
onClick={() => {
i18n.changeLanguage('de')
setEmailRequired(t('login.email_error_required'))
setEmailInvalid(t('login.email_error_invalid'))
setPasswordRequired(t('login.password_error_required'))
setvalidationSchema(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
}}
>
de
</button>
<button
type="button"
onClick={() => {
i18n.changeLanguage('en')
setEmailRequired(t('login.email_error_required'))
setEmailInvalid(t('login.email_error_invalid'))
setPasswordRequired(t('login.password_error_required'))
setvalidationSchema(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
}}
>
en
</button>
</div>
<div className="form-group col text-right">
<Link to="forgot-password" className="btn btn-link pr-0">
{t('login.forgot_password_label')}
</Link>
</div>
</div>
</div>
</Form>
)}
</Formik>
)
}
export { Login }
我尝试过的事情及其结果:
1.)
我曾经完全没有用过 useState,所以 validationSchema 是这样组成的:
const emailRequired = t('login.email_error_required');
const emailInvalid = t('login.email_error_invalid');
const passwordRequired = t('login.password_error_required');
const validationSchema = Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
})
这导致在输入其中一个输入之前只需单击 'de' 或 'en' 按钮即可将错误消息更改为新的 t运行slation。
2.)
我将语言更改按钮变成了提交类型的按钮:
<button
type="button"
onClick={() => {
i18n.changeLanguage('en')
}}
>
但这感觉不对。我无法想象那将是正确的解决方案。但是,它起作用了,因为它只是重新 运行 'OnSubmit' 函数...
3.)
将 'validationSchema' 和错误变量添加到 useState 和 useEffect 挂钩中的各种组合,希望它们中的任何一个都起作用,但它根本没有。
我想了解的是为什么 Formik 对象似乎 'insulate' 其内部组件来自 useEffect 带来的更新。为什么 React 没有看到验证错误的语言变化,但它所做的一切呢?
我还没有收到任何回复,但同时我自己想通了。
原来翻译键需要进入模式:
const validationSchema = Yup.object().shape({
email: Yup.string().email('login.email_error_invalid').required('login.email_error_required'),
password: Yup.string().required('login.password_error_required'),
})
然后错误消息需要是自定义组件。所以他们需要改变:
<ErrorMessage name="password" component="div" className="invalid-feedback" />
为此:
{errors.password && <div className="invalid-feedback">{t(errors.password)}</div>}
所以,对于电子邮件,整个事情看起来像:
<div className="form-group">
<label>{t('login.password_input_label')}</label>
<Field name="password" type="password" className={'form-control' + (errors.password && touched.password ? ' is-invalid' : '')} />
{errors.password && <div className="invalid-feedback">{t(errors.password)}</div>}
</div>
我不知道为什么这会有所不同,但希望它能在未来帮助其他人。
我正在向用户显示登录屏幕。这个登录屏幕(或整个应用程序,公平地说)可以用不同的语言显示。
除验证错误外,所有 UI 组件的语言切换工作正常。
目前,如果用户将电子邮件/密码字段留空,或输入无效的电子邮件地址,收到错误消息然后切换语言,错误消息不会更改为新语言。
经过大量猜测和实验后,我发现在单击 'de' 或 'en' 按钮两次,然后在输入中输入内容后,语言发生了变化。
我一辈子都想不出 useState 和 useEffect 挂钩的正确组合,使验证消息在按下任一语言按钮后立即显示 运行。
以下代码:
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Formik, Field, Form, ErrorMessage } from 'formik'
import * as Yup from 'yup'
import { useTranslation } from 'react-i18next'
import { accountService, alertService } from '@/_services'
function Login({ history, location }) {
//'common' is defined in i18n.js under init's namespace ('ns:') option
//if that's not set, use useTranslation('common'), or whatever the name
//of the json files you use in the language-specific folders is
const { t, i18n } = useTranslation()
const [emailRequired, setEmailRequired] = useState(t('login.email_error_required'))
const [emailInvalid, setEmailInvalid] = useState(t('login.email_error_invalid'))
const [passwordRequired, setPasswordRequired] = useState(t('login.password_error_required'))
const [validationSchema, setvalidationSchema] = useState(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
useEffect(() => {})
const initialValues = {
email: '',
password: '',
}
const onSubmit = ({ email, password }, { setSubmitting }) => {
alertService.clear()
accountService
.login(email, password)
.then(() => {
const { from } = location.state || { from: { pathname: '/' } }
history.push(from)
})
.catch((error) => {
setSubmitting(false)
alertService.error(error)
})
}
return (
<Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={onSubmit}>
{({ errors, touched, isSubmitting }) => (
<Form>
<h3 className="card-header">{t('login.page_title')}</h3>
<div className="card-body">
<div className="form-group">
<label>{t('login.email_input_label')}</label>
<Field name="email" type="text" className={'form-control' + (errors.email && touched.email ? ' is-invalid' : '')} />
<ErrorMessage name="email" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label>{t('login.password_input_label')}</label>
<Field name="password" type="password" className={'form-control' + (errors.password && touched.password ? ' is-invalid' : '')} />
<ErrorMessage name="password" component="div" className="invalid-feedback" />
</div>
<div className="form-row">
<div className="form-group col">
<button type="submit" disabled={isSubmitting} className="btn btn-primary">
{isSubmitting && <span className="spinner-border spinner-border-sm mr-1"></span>}
{t('login.login_btn_label')}
</button>
<Link to="register" className="btn btn-link">
{t('login.register_btn_label')}
</Link>
<button
type="button"
onClick={() => {
i18n.changeLanguage('de')
setEmailRequired(t('login.email_error_required'))
setEmailInvalid(t('login.email_error_invalid'))
setPasswordRequired(t('login.password_error_required'))
setvalidationSchema(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
}}
>
de
</button>
<button
type="button"
onClick={() => {
i18n.changeLanguage('en')
setEmailRequired(t('login.email_error_required'))
setEmailInvalid(t('login.email_error_invalid'))
setPasswordRequired(t('login.password_error_required'))
setvalidationSchema(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
}}
>
en
</button>
</div>
<div className="form-group col text-right">
<Link to="forgot-password" className="btn btn-link pr-0">
{t('login.forgot_password_label')}
</Link>
</div>
</div>
</div>
</Form>
)}
</Formik>
)
}
export { Login }
我尝试过的事情及其结果:
1.)
我曾经完全没有用过 useState,所以 validationSchema 是这样组成的:
const emailRequired = t('login.email_error_required');
const emailInvalid = t('login.email_error_invalid');
const passwordRequired = t('login.password_error_required');
const validationSchema = Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
})
这导致在输入其中一个输入之前只需单击 'de' 或 'en' 按钮即可将错误消息更改为新的 t运行slation。
2.)
我将语言更改按钮变成了提交类型的按钮:
<button
type="button"
onClick={() => {
i18n.changeLanguage('en')
}}
>
但这感觉不对。我无法想象那将是正确的解决方案。但是,它起作用了,因为它只是重新 运行 'OnSubmit' 函数...
3.)
将 'validationSchema' 和错误变量添加到 useState 和 useEffect 挂钩中的各种组合,希望它们中的任何一个都起作用,但它根本没有。
我想了解的是为什么 Formik 对象似乎 'insulate' 其内部组件来自 useEffect 带来的更新。为什么 React 没有看到验证错误的语言变化,但它所做的一切呢?
我还没有收到任何回复,但同时我自己想通了。
原来翻译键需要进入模式:
const validationSchema = Yup.object().shape({
email: Yup.string().email('login.email_error_invalid').required('login.email_error_required'),
password: Yup.string().required('login.password_error_required'),
})
然后错误消息需要是自定义组件。所以他们需要改变:
<ErrorMessage name="password" component="div" className="invalid-feedback" />
为此:
{errors.password && <div className="invalid-feedback">{t(errors.password)}</div>}
所以,对于电子邮件,整个事情看起来像:
<div className="form-group">
<label>{t('login.password_input_label')}</label>
<Field name="password" type="password" className={'form-control' + (errors.password && touched.password ? ' is-invalid' : '')} />
{errors.password && <div className="invalid-feedback">{t(errors.password)}</div>}
</div>
我不知道为什么这会有所不同,但希望它能在未来帮助其他人。