Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hook form - Field Array inside Dialog (Material UI)

So i have a form that has custom fields that i add via Field Array from react-hook-form. And everything works but i added drag and drop for the property items (to reorder them) and now it would be a big mess to show all these many fields directly so i moved them in a Dialog.

Here are the pictures to get the idea what is easier to drag n drop... (the right one)

The problem is that field array values get "reset" after the modal closes (after i type those form values in edit modal), i guess it has something to do with re-rendering but i am not sure.

I tried to show the minimal code example here without d&d and other useless stuff...
But here is codesandbox playground with the full code

CreateCategoryForm.js

const defaultValues = {
  name: "",
  slug: "",
  description: "",
  properties: [] // field array
}

function CreateCategoryForm() {
  const methods = useForm({ defaultValues });
  const { handleSubmit, control, errors } = methods;
  const { fields, append, remove, swap } = useFieldArray({ name: "properties", control });

  const onSubmit = async (data) => {
    console.log("data: ", data);
  };

  return (
    <Container>
        <FormProvider {...methods}>
          <form onSubmit={handleSubmit(onSubmit)} noValidate>
            <FormTextField name="name" />
            <FormTextField name="slug" />
            <FormTextField name="description" />

            {fields.map((card, idx) => (
              <PropertyCard key={card.id} card={card} idx={idx} errors={errors} remove={remove} />
            ))}

            <Button onClick={() => append({ name: "", label: "", type: "text", filterable: true })}>
                Add Property
            </Button>

            <FormSubmitButton>
              Create Category
            </FormSubmitButton>
          </form>
        </FormProvider>
    </Container>
  );
}

PropertyCard.js

function PropertyCard({ card, errors, idx, remove }) {
  const [dialogOpen, setDialogOpen] = React.useState(false);

  const handleOpenDialog = () => {
    setDialogOpen(true);
  };

  const handleCloseDialog = () => {
    setDialogOpen(false);
  };
 
return (
    <div>
      Property {idx + 1}
      <IconButton onClick={() => handleOpenDialog()}>
        edit
      </IconButton>
      <IconButton onClick={() => remove(idx)}>
        X
      </IconButton>

      <Dialog
        fullScreen
        open={dialogOpen}
        onClose={handleCloseDialog}
      >
        <Container maxWidth="xs">
          <FormTextField
            name={`properties[${idx}].name`}
            label="Property Name"
          />
          <FormTextField
            name={`properties[${idx}].label`}
            label="Property Label"
          />
          <FormSelect
            name={`properties[${idx}].type`}
            label="Filter Type"
            options={[
              { label: "text", value: "text" },
              { label: "bool", value: "bool" }
            ]}
            defaultValue="text"
          />
          <FormSwitch
            name={`properties[${idx}].filterable`}
            label="Filterable"
            defaultValue={true}
          />
          <IconButton onClick={handleCloseDialog}>
              X
          </IconButton>
        </Container>
      </Dialog>
    </div>
  );  
}
  • it is not the FormProvider context that is the issue or my FormTextField components... i tried with normal input with ref and it didn't work also.
  • also happens with no drag and drop code
like image 850
dankobgd Avatar asked Nov 27 '20 19:11

dankobgd


1 Answers

As @Bill mentioned in the comment, shouldUnregister: false seemed to do the trick.

So i changed useForm to this:

const methods = useForm({ defaultValues, shouldUnregister: false });

And for each property input i added defautlValue to make it work.

<FormTextField
  name={`properties[${idx}].name`}
  label="Property Name"
  defaultValue={getValues()?.properties?.[idx]?.name} // added this
/>
<FormTextField
  name={`properties[${idx}].label`}
  label="Property Label"
  defaultValue={getValues()?.properties?.[idx]?.label} // added this
/>
<FormSelect
  name={`properties[${idx}].type`}
  label="Filter Type"
  options={[
    { label: "text", value: "text" },
    { label: "bool", value: "bool" }
    ]}
  defaultValue={getValues()?.properties?.[idx]?.type || 'text'} // added this
/>
like image 162
dankobgd Avatar answered Oct 25 '22 03:10

dankobgd