You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Formik表单使用Hooks时Yup校验错误切换i18n语言异常如何解决

问题根因

  • i18n.changeLanguage 是异步执行的,你在调用该方法后立即读取t()翻译结果时,语言还未完成切换,拿到的依然是旧语言的文案,这就是你需要点击两次切换按钮才能拿到新文案的原因。
  • Formik会缓存已经生成的校验错误结果,即使你更新了校验规则,只要没有触发重新校验,已经存在的errors对象内容不会自动更新。其他UI内容可以正常切换语言,是因为i18n语言变更会触发组件重渲染,直接重新调用t()获取了新文案,而Formik缓存的旧错误没有被清除和重新生成,所以不会同步更新。
    你之前不用state直接定义validationSchema的方案,只有输入后才生效也是同理:语言切换后组件重渲染生成了新的校验规则,但Formik默认不会主动监听规则变化重新校验,只有用户输入触发单字段校验时,才会生成新语言的错误文案。

修复方案

核心逻辑调整

  1. 删除切换按钮点击回调里的state和schema更新逻辑,改用useEffect监听i18n当前语言变化,等语言切换完成后再更新校验规则
  2. 开启Formik的enableReinitialize属性,允许Formik在校验规则变更时刷新内部状态
  3. 拿到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 16:54:10