2

My goal is to select a Student and with onChange to dynamically setFieldValues for the FieldArray of their nested schedule array of objects.

When I select Student: 1, I should get an array of fields for each of their 3 schedule items, Student 2: will have example 4 items. Example relates to something such as this older example with Fruit on CodeSandbox. A second CodeSandbox also related for this.

My problem is that when I try to map a student's schedule it shows schedule.map is a TypeError. Thus not populating my FieldArray with the fields to be edited.

TypeError: values.schedule.map is not a function

My JSON data:

{
  "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",
        },
      }
    ]
  }

My Formik Component:

    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>

      )

}

  

Screenshots of my App: enter image description here

enter image description here

1 Answer 1

4

This is a tricky one 😉

It looks like you want to pre-populate a form with data fetched from the API. Also, you'd like the user to be able to switch between students (Tom and Nancy). In that case, you need to set all students' data inside Formik's initialValues. And when handling the class values inside the <FieldArray>, you need to ensure you are referring to the correct class for that particular student.

There are a few issues with your code, for example studentSchedule is never used and schedule is undefined. I went ahead and made some assumptions and came up with this solution below. It refers to the correct class by using two indexes inside the <FieldArray>: selectedStudentIndex and taskIndex

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

I'd be interested if anyone comes up with a simpler approach.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.