Formik React 在字段数组外使用 setFieldValue 设置值 onChange

Formik React set values onChange with setFieldValue outside Array of Fields

我的目标 是 select 一个 StudentonChange 动态 setFieldValues 对于 FieldArray 嵌套的 schedule 对象数组。

当我 select 学生:1 时,我应该为他们的 3 个计划项目中的每一个获取一个字段数组,学生 2:将有示例 4 项目。示例涉及诸如 older example with Fruit on CodeSandbox. A second CodeSandbox also related for this.

之类的内容

我的问题 是当我尝试 map 一个学生的 schedule 它显示 schedule.map 是一个 TypeError。因此不填充我的 FieldArray 要编辑的字段。

TypeError: values.schedule.map is not a function

我的JSON数据:

{
  "count": 2,
  "results": [
    {
      "id": "1",
      "name": "Tom",
      "schedule": [
        {
          "class": "GYM",
          "time": "9:00,
        },
        {
          "class": "History",
          "time": "10:00",
        },
        {
          "class": "Lunch",
          "time": "11:00",
        },
      },
    {
      "id": "2",
      "name": "Nancy",
      "schedule": [
        {
          "class": "Math",
          "time": "9:00,
        },
        {
          "class": "Science",
          "time": "10:00",
        },
        {
          "class": "English",
          "time": "11:00",
        },
        {
          "class": "History",
          "time": "12:00",
        },
      }
    ]
  }

我的 Formik 组件:

    const NewStudentSchedule = () => {
      const [studentSchedule, setSchedule] = useState([]);

      useEffect(() => {
        const getSchedule = async () => {
          try {
            const { data } = await fetchContext.authAxios.get(
              `/api/schedule/`
            );
            setSchedule(data.results);
            // console.log(data);
          } catch (err) {
            console.log(err);
          }
        };
        getSchedule();
      }, [fetchContext]);
      
      const initialValues = {
         id: '',
         schedule: [
           {
             class: '',
             time: '',
           },
         ],
       };

    return (
      <Formik
          initialValues={initialValues}
          onSubmit={async (values, { resetForm }) => {
            alert(JSON.stringify(values, null, 2));
            resetForm();
          }}
        >
          {({
            isSubmitting,
            values,
            setFieldValue,
            handleChange,
            errors,
            touched,
          }) => (
            <Form>
              <div className="row">
                <div className="col-md-6 mr-auto">
                  <div className="form-group">
                    <label htmlFor="status">STUDENT</label>
                    <Field
                      className="form-control"
                      as="select"
                      name="id"
                      onChange={(event) => {
                        handleChange(event);
                        const selectedTask = schedule.find(
                          (selectStudent) => selectStudent.id === event.target.value
                        );
                        setFieldValue(
                          `schedule.step`,
                          selectedTask.schedule.step
                        );
                      }}
                    >
                      <option value="" defaultValue disabled>
                        ...
                      </option>
                      {schedule &&
                        schedule.map((i) => (
                          <option key={i.id} value={i.id}>
                            {i.name}
                          </option>
                        ))}
                    </Field>
                  </div>
                </div>
              </div>
              <div className="col-md-12">
                <h3 className="pt-3">CREATE SCHEDULE</h3>
                <FieldArray name="schedule">
                  {({ insert, remove, push }) => (
                    <>
                      {values.schedule.length > 0 &&
                        values.schedule.map((task, index) => (
                          <div className="row" key={index}>
                            <div className="col-11">
                              <div className="row">
                                <div className="col">
                                  <div className="form-group">
                                    <label
                                      htmlFor={`schedule.${index}.class`}
                                    >
                                      CLASS
                                    </label>
                                    <Field
                                      className="form-control"
                                      name={`schedule.${index}.class`}
                                      type="text"
                                    />
                                  </div>
                                </div>
                                <div className="col">
                                  <div className="form-group">
                                    <label
                                      htmlFor={`schedule.${index}.time`}
                                    >
                                      TIME
                                    </label>
                                    <Field
                                      className="form-control"
                                      name={`schedule.${index}.time`}
                                      type="text"
                                    />
                                  </div>
                                </div>  
                              </div>
                            </div>
                            <div className="col-1">
                              <button
                                type="button"
                                className="btn delete"
                                onClick={() => remove(index)}
                              >
                                Delete
                              </button>
                            </div>
                          </div>
                        ))}
                      <button
                        type="button"
                        className="btn add"
                        onClick={() => push({ class: '', time: '' })}
                      >
                        Add
                      </button>
                    </>
                  )}
                </FieldArray>
                <button
                  className="btn float-right mb-5"
                  type="submit"
                  disabled={isSubmitting}
                >
                  SUBMIT
                </button>
              </div>
            </Form>
          )}
        </Formik>

      )

}

  

我的应用截图:

这是一个棘手的问题

您似乎想用从 API 获取的数据预填充表单。此外,您希望用户能够在学生(Tom 和 Nancy)之间切换。在这种情况下,您需要在 Formik 的 initialValues 中设置所有学生的数据。在处理 <FieldArray> 中的 class 值时,您需要确保您指的是特定学生的正确 class

您的代码存在一些问题,例如从未使用过 studentSchedule 并且 schedule 未定义。我继续做了一些假设,并在下面提出了这个解决方案。它通过在 <FieldArray> 中使用 两个 索引来引用正确的 class:selectedStudentIndextaskIndex

const App = () => {
  const [studentSchedule, setSchedule] = useState([]);

  useEffect(() => {
    const getSchedule = async () => {
      try {
        const { data } = await getData();
        setSchedule(data.results);
        // console.log(data);
      } catch (err) {
        console.log(err);
      }
    };
    getSchedule();
  }, []);

  return <ScheduleForm students={studentSchedule} />;
};

const ScheduleForm = ({ students }) => {
  const initialValues = {
    id: "",
    students
  };

  return (
    <Formik
      enableReinitialize={true}
      initialValues={initialValues}
      onSubmit={async (values, { resetForm }) => {
        console.log(JSON.stringify(values, null, 2));
      }}
    >
      {({
        isSubmitting,
        values,
        setFieldValue,
        handleChange,
        errors,
        touched
      }) => {
        const selectedStudentIndex = values.students.findIndex(
          ({ id }) => id === values.id
        );
        return (
          <Form>
            <div className="row">
              <div className="col-md-6 mr-auto">
                <div className="form-group">
                  <label htmlFor="status">STUDENT</label>
                  <Field
                    className="form-control"
                    as="select"
                    name="id"
                    onChange={(event) => {
                      handleChange(event);
                      const selectedTask = students.find(
                        (selectStudent) =>
                          selectStudent.id === event.target.value
                      );
                      setFieldValue(
                        `schedule.step`,
                        selectedTask.schedule.step
                      );
                    }}
                  >
                    <option value="" defaultValue disabled>
                      ...
                    </option>
                    {values.students &&
                      values.students.map((i) => (
                        <option key={i.id} value={i.id}>
                          {i.name}
                        </option>
                      ))}
                  </Field>
                </div>
              </div>
            </div>
            <div className="col-md-12">
              <h3 className="pt-3">CREATE SCHEDULE</h3>
              <FieldArray name={`students.${selectedStudentIndex}.schedule`}>
                {({ insert, remove, push }) => (
                  <div>
                    {values.id !== "" &&
                      values.students[selectedStudentIndex].schedule.map(
                        (task, taskIndex) => (
                          <div className="row" key={taskIndex}>
                            <div className="col-11">
                              <div className="row">
                                <div className="col">
                                  <div className="form-group">
                                    <label
                                      htmlFor={`students.${selectedStudentIndex}.schedule.${taskIndex}.class`}
                                    >
                                      CLASS
                                    </label>
                                    <Field
                                      className="form-control"
                                      name={`students.${selectedStudentIndex}.schedule.${taskIndex}.class`}
                                      type="text"
                                    />
                                  </div>
                                </div>
                                <div className="col">
                                  <div className="form-group">
                                    <label
                                      htmlFor={`students.${selectedStudentIndex}.schedule.${taskIndex}.time`}
                                    >
                                      TIME
                                    </label>
                                    <Field
                                      className="form-control"
                                      name={`students.${selectedStudentIndex}.schedule.${taskIndex}.time`}
                                      type="text"
                                    />
                                  </div>
                                </div>
                              </div>
                            </div>
                            <div className="col-1">
                              <button
                                type="button"
                                className="btn delete"
                                onClick={() => remove(taskIndex)}
                              >
                                Delete
                              </button>
                            </div>
                          </div>
                        )
                      )}
                    <button
                      type="button"
                      className="btn add"
                      onClick={() => push({ class: "", time: "" })}
                    >
                      Add
                    </button>
                  </div>
                )}
              </FieldArray>
              <button
                className="btn float-right mb-5"
                type="submit"
                disabled={isSubmitting}
              >
                SUBMIT
              </button>
            </div>
          </Form>
        );
      }}
    </Formik>
  );
};

Live Demo

如果有人想出更简单的方法,我会很感兴趣。