question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

useFieldArray clears input values when element is removed

See original GitHub issue

Describe the bug I use useFieldArray to render a set of inputs that are based on a “map-object”. This object describes how the inputs should be layouted, which inputs should be rendered, which of them are required and so on:

const contentConfig = {
  contentMap: [
    {
      key: "address",
      value: "Address",
      fields: [
        [
          {
            key: "street",
            value: "Street",
            field: {
              type: "text",
              options: null
            },
            isRequired: null,
            width: 6
          }
        ],
        [
          {
            key: "zip_code",
            value: "ZIP",
            field: {
              type: "text",
              options: null
            },
            isRequired: null,
            width: 6
          }
        ],
        [
          {
            key: "label",
            value: "Label",
            field: {
              type: "freeSolo",
              options: ["Work", "Private", "Other"]
            },
            isRequired: null,
            width: 12
          }
        ]
      ]
    }
  ]
};

My FieldArray component renders the inputs for each Element based on this map …

const FieldArray = ({ formMethods, name, arrayMap, ...rest }) => {
  const { fields, append, remove } = useFieldArray({
    control: formMethods.control,
    name
  });

  const handleAddElement = () => {
    append();
  };

  const handleDeleteElement = index => {
    remove(index);
  };

  return (
    <Fragment>
      {fields.map((field, index) => {
        const fieldName = `${name}[${index}]`;
        return (
          <Grid
            container
            spacing={1}
            key={field.id}
            style={{ marginBottom: "3rem" }}
          >
            <Grid item container spacing={2} xs={6}>
              {arrayMap.map(row => {
                return row.map(element => {
                  const newElem = { ...element };

                  newElem.key = `${fieldName}.${element.key}`;
                  newElem.value = `${element.value}`;

                  const renderedInput = getInputElement(
                    newElem,
                    formMethods,
                    rest
                  );

                  return (
                    <Grid
                      item
                      xs={element.width}
                      zeroMinWidth
                      key={newElem.key}
                    >
                      {renderedInput}
                    </Grid>
                  );
                });
              })}
            </Grid>
            <Grid xs={2} item zeroMinWidth>
              <IconButton onClick={() => handleDeleteElement(index)}>
                <DeleteIcon />
              </IconButton>
            </Grid>
          </Grid>
        );
      })}

      <Button onClick={handleAddElement} color="primary">
        Add
      </Button>
    </Fragment>
  );
};

… and returns either CustomInput or CustomFreeSolo:


const CustomInput = ({
  name,
  label,
  isRequired,
  register,
  errors,
  type,
  ...rest
}) => {
  return (
    <TextField
      {...rest}
      name={name}
      required={Boolean(isRequired?.required)}
      id={name}
      type={type}
      label={label}
      inputRef={register(isRequired)}
    />
  );
};

const useDefaultValue = (getValues, name) => {
  const value = getValues(name);
  return value ? value : "";
};

const CustomFreeSolo = ({
  name,
  label,
  options,
  autoComplete,
  isRequired,
  getValues,
  type,
  control,
  selectOnFocus,
  openOnFocus,
  ...rest
}) => {
  const defaultValue = useDefaultValue(getValues, name);

  const handleChange = (_, newValue) => {
    return newValue;
  };

  return (
    <Controller
      name={name}
      rules={isRequired}
      control={control}
      defaultValue={defaultValue}
      onChange={([_, data]) => handleChange(_, data)}
      as={
        <Autocomplete
          id={name}
          freeSolo
          options={options}
          autoSelect
          includeInputInList
          openOnFocus={openOnFocus}
          selectOnFocus={selectOnFocus}
          autoComplete={autoComplete}
          fullWidth={rest.fullWidth}
          renderInput={params => {
            return (
              <TextField
                {...params}
                {...rest}
                required={Boolean(isRequired?.required)}
                label={label}
                type={type}
              />
            );
          }}
        />
      }
    />
  );
};

const getInputElement = (element, formMethods, rest) => {
  const { register, control, errors, getValues } = formMethods;

  const {
    key: name,
    value: label,
    field: { type, options },
    isRequired
  } = element;

  switch (type) {
    case "text":
      return (
        <CustomInput
          {...{
            name,
            label,
            isRequired,
            register,
            errors,
            type
          }}
          {...rest}
        />
      );

    case "freeSolo":
      return (
        <CustomFreeSolo
          {...{
            name,
            label,
            isRequired,
            control,
            errors,
            type,
            options,
            getValues
          }}
          {...rest}
        />
      );

    case "id":
      return <input ref={register(isRequired)} name={name} hidden />;

    default:
      return null;
  }
};

Now when I want to remove an element, all the elements that come after the removed one are either cleared or set to the default values entered in useForm.

To Reproduce

  1. Go to sandbox link and remove an element

Codesandbox link (Required) https://codesandbox.io/s/react-hook-form-fieldarray-bug-ti0td?file=/src/App.js

Expected behavior The fields should not be cleared after deleting an element.

Desktop (please complete the following information):

  • OS: Win 10
  • Browser opera, chrome
  • Version latest

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:14 (6 by maintainers)

github_iconTop GitHub Comments

3reactions
chrishoermanncommented, Jun 8, 2020

Sometimes the solution is very simple:

I used the index of the last .map method as key for my Grid item and now everything works fine:

return (
    <Fragment>
      {fields.map((field, index) => {
        const fieldName = `${name}[${index}]`;
        return (
          <Grid
            container
            spacing={1}
            key={field.id}
            style={{ marginBottom: "3rem" }}
          >
            <Grid item container spacing={2} xs={6}>
              {arrayMap.map(row => {
                return row.map((element, idx) => {
                  const newElem = { ...element };

                  newElem.key = `${fieldName}.${element.key}`;
                  newElem.value = `${element.value}`;

                  const renderedInput = getInputElement(
                    newElem,
                    formMethods,
                    rest
                  );

                  return (
                    <Grid
                      item
                      xs={element.width}
                      zeroMinWidth
                      key={idx} // index from .map function needs to be used
                    >
                      {renderedInput}
                    </Grid>
                  );
                });
              })}
            </Grid>
            <Grid xs={2} item zeroMinWidth>
              <IconButton onClick={() => handleDeleteElement(index)}>
                <DeleteIcon />
              </IconButton>
            </Grid>
          </Grid>
        );
      })}

      <Button onClick={handleAddElement} color="primary">
        Add
      </Button>
    </Fragment>
  );

Updated Sandbox: https://codesandbox.io/s/react-hook-form-fieldarray-bug-53279?file=/src/FieldArray.js

1reaction
chrishoermanncommented, Jun 8, 2020

OK, thanks I’ll try it out

Read more comments on GitHub >

github_iconTop Results From Across the Web

UsefieldArray react hook form deleting the last element only
Basically I'm trying to append and delete but the problem is when I'm deleting its deleting only the last element. append is working...
Read more >
useFieldArray - Simple React forms validation
Remove input /inputs at particular position, or remove all when no index provided. Rules. useFieldArray automatically generates a unique identifier named id ...
Read more >
React Hook Form 7 - Dynamic Form Example with useFieldArray
This is a quick example of how to build a dynamic form with validation in React with version 7 of the React Hook...
Read more >
Field Arrays Example - Redux Form
array object, and as actions bound to both the form and array on the object provided by the FieldArray component: insert , pop...
Read more >
Effective forms: building dynamic array fields with useFieldArray
This was the final article of the React Hook Form series. ... value of any field with watch and useWatch; Applying remove to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found