Formik表单使用Hooks时Yup校验错误切换i18n语言异常如何解决
问题根因
i18n.changeLanguage是异步执行的,你在调用该方法后立即读取t()翻译结果时,语言还未完成切换,拿到的依然是旧语言的文案,这就是你需要点击两次切换按钮才能拿到新文案的原因。- Formik会缓存已经生成的校验错误结果,即使你更新了校验规则,只要没有触发重新校验,已经存在的
errors对象内容不会自动更新。其他UI内容可以正常切换语言,是因为i18n语言变更会触发组件重渲染,直接重新调用t()获取了新文案,而Formik缓存的旧错误没有被清除和重新生成,所以不会同步更新。
你之前不用state直接定义validationSchema的方案,只有输入后才生效也是同理:语言切换后组件重渲染生成了新的校验规则,但Formik默认不会主动监听规则变化重新校验,只有用户输入触发单字段校验时,才会生成新语言的错误文案。
修复方案
核心逻辑调整
- 删除切换按钮点击回调里的state和schema更新逻辑,改用useEffect监听i18n当前语言变化,等语言切换完成后再更新校验规则
- 开启Formik的
enableReinitialize属性,允许Formik在校验规则变更时刷新内部状态 - 拿到Formik实例,语言切换后手动触发全表单校验,直接更新已存在的错误文案
完整修改后代码
import React, { useState, useEffect, useRef } 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 }) { const { t, i18n } = useTranslation() // 存储Formik实例用于手动触发校验 const formikRef = useRef(null) // 统一生成最新语言版本的校验规则 const getValidationSchema = () => Yup.object().shape({ email: Yup.string().email(t('login.email_error_invalid')).required(t('login.email_error_required')), password: Yup.string().required(t('login.password_error_required')), }) const [validationSchema, setValidationSchema] = useState(getValidationSchema()) // 监听语言变化,更新校验规则并触发重新校验 useEffect(() => { const newSchema = getValidationSchema() setValidationSchema(newSchema) // 表单已有交互的情况下主动触发校验,直接更新错误文案 if (formikRef.current && (formikRef.current.touched.email || formikRef.current.touched.password)) { formikRef.current.validateForm() } }, [i18n.language]) 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} enableReinitialize={true} ref={formikRef} > {({ 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')} > de </button> <button type="button" onClick={() => i18n.changeLanguage('en')} > 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 }
内容的提问来源于stack exchange,提问作者user17378870
相关产品推荐
相关产品推荐

