Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Hook Form Register Different Forms With Same Field Names

I have a material ui stepper in which there are multiple forms using react-hook-form. When I change between steps, I would expect a new useForm hook to create new register functions that would register new form fields, but when I switch between steps, the second form has the data from the first. I've seen at least one question where someone was trying to create two forms on the same page within the same component but in this case I am trying to create two unique forms within different steps using different instances of a component. It seems like react-hook-form is somehow not updating the useForm hook or is recycling the form fields added to the first register call.

Why isn't react-hook-form using a new register function to register form fields to a new useForm hook? Or at least, why isn't a new useForm hook being created between steps?

DynamicForm component. There are two of these components (one for each step in the stepper).

import { Button, Grid } from "@material-ui/core";
import React from "react";
import { useForm } from "react-hook-form";
import { buttonStyles } from "../../../styles/buttonStyles";

import AppCard from "../../shared/AppCard";
import { componentsMap } from "../../shared/form";

export const DynamicForm = (props) => {
  const buttonClasses = buttonStyles();
  const { defaultValues = {} } = props;
  const { handleSubmit, register } = useForm({ defaultValues });

  const onSubmit = (userData) => {
    props.handleSubmit(userData);
  };

  return (
    <form
      id={props.formName}
      name={props.formName}
      onSubmit={handleSubmit((data) => onSubmit(data))}
    >
      <AppCard
        headline={props.headline}
        actionButton={
          props.actionButtonText && (
            <Button className={buttonClasses.outlined} type="submit">
              {props.actionButtonText}
            </Button>
          )
        }
      >
        <Grid container spacing={2}>
          {props.formFields.map((config) => {
            const FormComponent = componentsMap.get(config.component);
            return (
              <Grid key={`form-field-${config.config.name}`} item xs={12}>
                <FormComponent {...config.config} register={register} />
              </Grid>
            );
          })}
        </Grid>
      </AppCard>
    </form>
  );
};

N.B. The images are the same because the forms will contain the same information, i.e. the same form fields by name.

Entry for first form:

enter image description here

Entry for the second form:

enter image description here

Each form is created with a config like this:

{
  component: DynamicForm,
  label: "Stepper Label",
  config: {
    headline: "Form 1",
    actionButtonText: "Next",
    formName: 'form-name',
    defaultValues: defaultConfigObject,
    formFields: [
      {
        component: "AppTextInput",
        config: {
          label: "Field 1",
          name: "field_1",
        },
      },
      {
        component: "AppTextInput",
        config: {
          label: "Field2",
          name: "field_2",
        },
      },
      {
        component: "AppTextInput",
        config: {
          label: "Field3",
          name: "field_3",
        },
      },
      {
        component: "AppTextInput",
        config: {
          label: "Field4",
          name: "field4",
        },
      },
    ],
    handleSubmit: (formData) => console.log(formData),
  },
},

And the active component in the steps is handled like:

import { Button, createStyles, makeStyles, Theme } from "@material-ui/core";
import React, { useContext, useEffect, useState } from "react";
import { StepperContext } from "./StepperProvider";

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    buttonsContainer: {
      margin: theme.spacing(2),
    },
    buttons: {
      display: "flex",
      justifyContent: "space-between",
    },
  })
);
export const StepPanel = (props) => {
  const { activeStep, steps, handleBack, handleNext, isFinalStep } = useContext(
    StepperContext
  );

  const [activeComponent, setActiveComponent] = useState(steps[activeStep]);

  const classes = useStyles();

  useEffect(() => {
    setActiveComponent(steps[activeStep]);
  }, [activeStep]);

  return (
    <div>
      <activeComponent.component {...activeComponent.config} />
      {
        isFinalStep ? (
          <div className={classes.buttonsContainer}>
            <div className={classes.buttons}>
              <Button disabled={activeStep === 0} onClick={handleBack}>
                Back
              </Button>
              <Button
                variant="contained"
                color="primary"
                onClick={props.finishFunction}
              >
                Finish And Submit
              </Button>
            </div>
          </div>
        ) 
        : 
        null
      }
    </div>
  );
};
like image 309
tlm Avatar asked Aug 24 '26 13:08

tlm


1 Answers

From your image, every form looks the same. You should try providing a unique key value to your component so React know that each form is different. In this case it can be the step number for example:

<activeComponent.component {...props} key='form-step-1'>
like image 154
NearHuscarl Avatar answered Aug 26 '26 04:08

NearHuscarl