给出 onChange 选项时 Formik 中 select 字段的问题

Problem with select Field in Formik when onChange option given

我的 <Field as="select"> 在 Formik 中有问题。 onChange 效果不佳 - 无论我选择什么选项,当我提交表单时,它都会将 player1player2 作为空字符串发送。

例如来自 console.log(values) 的输出:

{ id: '10', player1: '', player2: '', winner: '1', date: '2021-12-16' }

当我删除 onChange 时效果很好,但我需要 onChange 和 selectedPlayer 状态,因为如果我选择 player1,我不希望他在第二个选项中可见,并且反之亦然。 为什么在更改 selectedPlayervalue 属性 在我的 Field 中没有更改?似乎它从空字符串 selectedPlayer 获取初始值。

import { Field, Form, Formik } from 'formik';
import { connect } from 'react-redux';
import {addMatchAction} from "../../ducks/matches/operations";
import {getAllPlayers} from "../../ducks/players/selectors";
import {useState} from "react";
const MatchesForm = ({ addMatchAction,error,players }, props) => {
    const handleSubmit = (values) => {

        console.log(values)
        addMatchAction(values);
    }

    const [selectedPlayer1,setSelectedPlayer1] = useState('')
    const [selectedPlayer2,setSelectedPlayer2] = useState('')
    return (
        <div>
            <h3>Add match</h3>
            <Formik
                initialValues={{
                    id: '',
                    player1: '',
                    player2: '',
                    winner: '',
                    date: ''
                }}
                onSubmit={(values) => handleSubmit(values)}
                enableReinitialize={true}>
                <Form>
                    <label htmlFor="id">Id: </label>
                    <Field name="id" />
                    <label htmlFor="player1">Player 1: </label>
                    <Field as="select" name="player1" onChange={(e) => setSelectedPlayer1(e.target.value)} value={selectedPlayer1}>
                        <option disabled value="">(Select a player 1)</option>
                        {players && players.map(player => {
                            if (player.id !== selectedPlayer2) {
                                return (
                                    <option value={player.id}>{player.firstName} {player.lastName}</option>
                                )
                            }
                        }) }
                    </Field>
                    <p>
                        <label htmlFor="player2">Player 2: </label>
                        <Field as="select" name="player2" onChange={(e) => setSelectedPlayer2(e.target.value)} value={selectedPlayer2}>
                        <option disabled value="">(Select a player 2)</option>
                        {players && players.map(player => {
                            if (player.id !== selectedPlayer1) {
                                return (
                                    <option value={player.id}>{player.firstName} {player.lastName}</option>
                                )
                        }
                        }) }
                        </Field>
                    </p>
                    <label htmlFor="winner">Winner: </label>
                    <Field as="select" name="winner">
                        <option disabled value="">Pick a winner</option>
                        {players && players.map(player => {
                            if (player.id === selectedPlayer1 || player.id === selectedPlayer2 ) {
                                return (
                                    <option value={player.id}>{player.firstName} {player.lastName}</option>
                                )
                            }
                        })}
                    </Field>
                    <label htmlFor="date">Date: </label>
                    <Field name="date" type="date" />
                    <button type="submit">
                        Zatwierdz
                    </button>
                </Form>
            </Formik>
            <p>{error && (<span>{error.name} {error.response.message}</span>)}</p>
        </div>
    )
}

const mapStateToProps = (state) => {
    return {
        error: state.error.error,
        players: getAllPlayers(state)
    }
}

const mapDispatchToProps = ({
    addMatchAction
});


export default connect(mapStateToProps, mapDispatchToProps)(MatchesForm);

如果您为 Formik 设置 onChange 处理程序,那么您将覆盖默认处理程序,并且需要手动 setFieldValue

onChange={(e) => {
  setSelectedPlayer1(e.target.value)
  setFieldValue('player1', e.target.value)
}}

作为替代方案,您可以做的是让 Formik 成为最顶层的父组件,这将允许它的所有子组件像这样使用它的上下文:

const ChildComponent = () => {
  const { values } = useFormikContext()
  const player1Value = values['player1']
  return <div>Player 1 is: {player1Value}</div>
}

这样,您就不必像使用 useState 那样进行额外的状态管理,它已经在 Formik 的上下文中为您保留。

看这里:https://formik.org/docs/api/useFormikContext

我认为您不了解 Formik 和 Field 组件的工作原理。 Formik 使用它自己的内部状态来处理所有 Form 状态,因此您应该使用它并避免在这种情况下使用 useState。您可以执行以下操作:

  • 删除 onChange 处理程序和值,因为它们不允许 Formik 完成它的工作。
  • 在允许的情况下使用 Formik 组件下的函数,它将允许您访问表单状态值。

这是代码。我还重构了 array.map 函数,因为在这些情况下使用数组过滤器更正确(地图应始终 return 与数组中相同数量的元素)

const MatchesForm = ({ addMatchAction,error,players }, props) => {
  const handleSubmit = (values) => {
      console.log('submit', values)
      addMatchAction(values);
  }

  return (
      <div>
          <h3>Add match</h3>
          <Formik
              initialValues={{
                  id: '',
                  player1: '',
                  player2: '',
                  winner: '',
                  date: ''
              }}
              onSubmit={(values) => handleSubmit(values)}
              enableReinitialize={true}>
                  {props => {
                      // Try a console.log here to see props and props.values and you will see them updating on every change
                      // console.log('Values', props.values);

                      return (
                        <Form>
                        <label htmlFor="id">Id: </label>
                        <Field name="id" />
                        <label htmlFor="player1">Player 1: </label>
                        <Field as="select" name="player1">
                            <option disabled value="">(Select a player 1)</option>
                            {players && players.filter(player => player.id !== props.values.player2).map(player => (
                                <option value={player.id}>{player.firstName} {player.lastName}</option>
                            ))}
                        </Field>
                        <p>
                            <label htmlFor="player2">Player 2: </label>
                            <Field as="select" name="player2" >
                            <option disabled value="">(Select a player 2)</option>
                            {players && players.filter(player => player.id !== props.values.player1).map(player => (
                                <option value={player.id}>{player.firstName} {player.lastName}</option>
                            ))}
                            </Field>
                        </p>
                        <label htmlFor="winner">Winner: </label>
                        <Field as="select" name="winner">
                            <option disabled value="">Pick a winner</option>
                            {players && players.filter(player => player.id === props.values.player1 || player.id === props.values.player2).map(player => (
                                <option value={player.id}>{player.firstName} {player.lastName}</option>
                            ))}
                        </Field>
                        <label htmlFor="date">Date: </label>
                        <Field name="date" type="date" />
                        <button type="submit">
                            Zatwierdz
                        </button>
                    </Form>
          
                      )
                  }}
          </Formik>
          <p>{error && (<span>{error.name} {error.response.message}</span>)}</p>
      </div>
  )
}