2

I'm trying to use React-hooks-form to build a multi step form in which one of the steps has a select menu that prompts a selection, and two other text fields.

The step within the larger form has:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useForm from "react-hook-form";
import { withRouter } from "react-router-dom";
import { useStateMachine } from "little-state-machine";
import updateAction from "./updateAction";
import { Form, Button, Divider, Layout, Typography, Skeleton, Switch, Card, Icon, Avatar } from 'antd';
import Select from "react-select";



const { Content } = Layout 
const { Text, Paragraph } = Typography;
const { Meta } = Card;





const defaultValue = {
  explain: "",
  managementPlan: ""
};

const issueOptions = [
  { value: "riskofharm", label: "Risk of harm" },
  { value: "informedconsent", label: "Informed consent" },
  { value: "anon", label: "Anonymity and Confidentiality" },
  { value: "deceptive", label: "Deceptive practices" },
  { value: "withdrawal", label: "Right to withdraw" },
  { value: "none", label: "No ethics considerations" }
];

const Budget = props => {
  const [ethics, setEthics] = useState([]);
  const [issues, setIssues] = useState([]);
  const { action } = useStateMachine(updateAction);
  const { register, handleSubmit, setValue, getValues, clearError } = useForm();

  useEffect(() => {
    register({ name: "issues" });
  }, [register]);

  // const onSubmit = data => {
  //   console.log("submit", data);
  const onSubit = data => {
    // console.log("submit", data);
    // console.log("combined", formValues);


    // combine your ethicsIssue to formValues
    const { ethics, issues } = data;
    const formValues = {
      ethics: ethics.map((ethic, index) => ({
        ...ethic,
        issue: issues[index]
      }))
    };
    action(data);
    props.history.push("./ProposalOutcomes");

  };               



  const handleChange = index => selectecIssue => {
    const issuesCopy = [...issues];
    issuesCopy[index] = selectecIssue;

    setIssues(issuesCopy);
    setValue("issues", issuesCopy);
  };

  const addEthic = async () => {
    setEthics([...ethics, defaultValue]);
  };

  const removeEthic = index => () => {
    // get values
    const { ethics, issues } = getValues({ nest: true });

    // create a copy
    const newEthics = [...(ethics || [])];
    const newIssues = [...(issues || [])];

    // remove by index
    newEthics.splice(index, 1);
    newIssues.splice(index, 1);

    // update values
    setEthics(newEthics);
    setIssues(newIssues);

    for (let i = 0; i < newEthics.length; i++) {
      // we register the field using ethics[i].explain
      // therefore, we need to setValue that way
      setValue(`ethics[${i}].explain`, newEthics[i].explain);
      setValue(`ethics[${i}].managementPlan`, newEthics[i].managementPlan);
    }

    // same goes with issue
    setValue("issues", newIssues);
  };

  const clearEthics = () => {
    setEthics([]);
    setIssues([]);
    setValue("issues", []);
    clearError();
  };

  return (
    <div>
      <Content
        style={{
          background: '#fff',
          padding: 24,
          margin: "auto",
          minHeight: 280,
          width: '70%'
        }}
      >
      <Paragraph>
        <h2>Design Studio</h2>
        <h4 style={{ color: '#506e8d'}}>Design a Research Proposal</h4>
        </Paragraph>
        <Divider />

        <h2>Part 10: Ethics</h2>


    <form onSubmit={handleSubmit(onSubit)}>
      {ethics.map((_, index) => {
        const fieldName = `ethics[${index}]`;

        return (
          <fieldset name={fieldName} key={fieldName}>
            <label>
              Issue {index}:
              <Select
                placeholder="Select One"
                value={issues[index]}
                options={issueOptions}
                onChange={handleChange(index)}
              />
            </label>

            <label>
              Explain {index}:
              <input type="text" name={`${fieldName}.explain`} ref={register} />
            </label>

            <label>
              Management Plan {index}:
              <input
                type="text"
                name={`${fieldName}.managementPlan`}
                ref={register}
              />
            </label>

            <Button type="danger" style={{ marginBottom: '20px', float: 'right'}} onClick={removeEthic(index)}>
              Remove Ethic
            </Button>
          </fieldset>
        );
      })}
      <div className="action">
        <Button type="primary" style={{ marginBottom: '20px'}} onClick={addEthic}>
          Add Ethic
        </Button>
        <br />
        <Button type="button" style={{ marginBottom: '20px'}} onClick={clearEthics}>
          Clear Ethics
        </Button>
      </div>
      <input type="submit" value="next - outcomes" />

    </form>
    </Content>
    </div>
  );
}



export default withRouter(Budget);

I'm having trouble producing a single array with the content from the 3 fields.

Currently, I get a json packet as set out below (one array over ethics and another over issues):

"ethics": [
{
"explain": "hel",
"managementPlan": "hello"
},
{
"explain": "hiiii",
"managementPlan": "hi"
}
]
,
"issues": [
{
"value": "withdrawal",
"label": "Right to withdraw"
},
{
"value": "deceptive",
"label": "Deceptive practices"
}
]

I'm stuck on this form with an error that says: cannot read map of undefined.

The error message is highlighting a problem pointing to this line:

  {ethics.map((_, index) => {

I'm expecting the on submit function to push the 3 form fields in a single array to the updateAction, but that's not happening and I can't see what I need to do to get this working.

Has anyone successfully integrated a select menu with a repeatable form field using field arrays in react-hook-forms?

1 Answer 1

2

I have included a new section for Field Array and we are also working on a new custom hook for useFieldArray. https://react-hook-form.com/advanced-usage#FieldArrays

Here is a code snippet:

import React, { useState } from "react";
import { useForm } from "react-hook-form";

function createArrayWithNumbers(length) {
  return Array.from({ length }, (_, k) => k + 1);
}

export default function App() {
  const { register, handleSubmit } = useForm();
  const [size, setSize] = useState(1);
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {createArrayWithNumbers(size).map(index => {
        return (
          <>
            <label htmlFor="firstName">First Name</label>
            <input
              name={`firstName[${index}]`}
              placeholder="first name"
              ref={register({ required: true })}
            />

            <label htmlFor="lastName">Last Name</label>
            <input
              name={`lastName[${index}]`}
              placeholder="last name"
              ref={register({ required: true })}
            />
          </>
        );
      })}

      <button type="button" onClick={() => setSize(size + 1)} >
        Add Person
      </button>

      <input type="submit" />
    </form>
  );
}

This codesandbox also shows advance usage: https://codesandbox.io/s/react-hook-form-field-array-advanced-with-delete-insert-append-edit-l19pz

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.