2

I am working on building a large form application.  The issue is that the form consists of many nested forms. The first stage/ form is similar for all users. Then, depending on the user's input, a different form will show up. Meaning each form has its own local state, and upon submitting each and every form the global state is being updated.

for clarification see a small part of the global state below:

const [state, setState] = useState({
    startForm {
      email: "",
      priority: "",
      requestType: "",
      dueDate: "",
    },
    designRequest: {
      designType: "",
      yellow: { client:"" , message: "" },
      red: { change: "", parameter: "" }
    }
  }
  );


const handleStartStepData = (startStepData) => { var startStep = {...state.startForm} state.startForm = startStepData setState({ startStep })
 };

return ( <StartForm state={startStepState} onChange={handleStartStepData}/>

)

start form:

export const StartForm = (props) => {
  
  const methods = useForm();

  const [state, setState] = useState({
    email: "",
    priority: "",
    requestType: "",
    dueDate: "",
   
  });


  const onSubmit = (data) => {
    const startFormState = data;
    props.onChange(startFormState);
  };

after filling out the start form (email, priority,requestType, and due date), and depending on the requestType a different form will show up on the screen. So if for example, the user chose a design request, they will then choose between a yellow and red request.  I am using react forms hook along with material UI components. I tried setting up a handle submit for each small form on the parent(global form) but each of them overrides the global state instead of merging it. I am a newbie in react and could use any idea you have on better managing the state in this application.

Limor
  • 119
  • 1
  • 2
  • 8

1 Answers1

1

the correct approach is to use the FormProvider and useFormContext. All of your small forms should be nested in the FormProvider.

See example on codesandbox - https://codesandbox.io/s/react-hook-form-v7-form-context-forked-t02g0?file=/src/index.js:0-1602

Documentation = https://react-hook-form.com/api/useformcontext

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { useForm, FormProvider, useFormContext } from "react-hook-form";

import "./styles.css";

export default function App() {
  const methods = useForm();
  const [formData, setFormData] = useState();
  const onSubmit = (data) => setFormData(data);

  console.log(methods.formState.errors);

  return (
    <>
      <FormProvider {...methods}>
        {" "}
        {/* // pass all methods into the context */}
        <form onSubmit={methods.handleSubmit(onSubmit)}>
          <NestedNameEmailForm />
          <NestedAddressForm />
          <input type="submit" />
        </form>
      </FormProvider>
      <p>DATA</p>
      <pre style={{ color: "white" }}>{JSON.stringify(formData, null, 2)}</pre>
      <p>ERRORS</p>
      <pre style={{ color: "white" }}>
        {JSON.stringify(Object.keys(methods.formState.errors), null, 2)}
      </pre>
    </>
  );
}

function NestedNameEmailForm() {
  const { register } = useFormContext(); // retrieve all hook methods
  return (
    <>
      <input
        {...register("first", {
          required: true
        })}
        placeholder="First"
      />
      <input
        {...register("last", {
          required: true
        })}
        placeholder="Last"
      />
      <input
        {...register("email", {
          required: true
        })}
        placeholder="Email"
      />
    </>
  );
}

function NestedAddressForm() {
  const { register } = useFormContext(); // retrieve all hook methods
  return (
    <>
      <input
        {...register("address_line_1", {
          required: true
        })}
        placeholder="Address Line 1"
      />
      <input {...register("address_line_2")} placeholder="Address Line 1" />
      <input
        {...register("city", {
          required: true
        })}
        placeholder="City"
      />
      <input
        {...register("state", {
          required: true
        })}
        placeholder="State"
      />
      <input
        {...register("zip", {
          required: true
        })}
        placeholder="Zip"
      />
    </>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Aaron Saunders
  • 33,180
  • 5
  • 60
  • 80
  • Thanks! I tried using this approach but I can't figure out how to handle the conditional rendering of the small forms. What I am trying to achieve is that each form will have its own local state. so once it's completed it will update the global state. Then I would be able to check the global state and based on its status I will know which form I should present next. Does each small form should also have a form provider or only the global one? – Limor Apr 21 '21 at 14:13
  • you an use Yup as the validation resolver and it supports conditional validation of fields. rolling your own state manager based on which forms you want to show will be a nightmare to maintain IMHO - https://stackoverflow.com/questions/49394391/conditional-validation-in-yup – Aaron Saunders Apr 21 '21 at 18:01