skip to Main Content

I have a basic form where the code for it is using react-hook-form and looks like:

import "./styles.css";
import { useForm, useFieldArray, Control, Controller } from 'react-hook-form';
import { useState } from 'react';

const defaultValues = {
  "foo": [
    {
      "bar": [0, 0, 0]
    },
    {
      "bar": [4, 5, 6]
    }
  ]
}


const FooComponent = ({fooIndex, control}) => {
  const { fields, append, remove } = useFieldArray({
    control,
    name: `foo.${fooIndex}.bar`,
    keyName: 'id'
  });

  return (
    <div>
      <h1>Foo</h1>
      {fields.map((field, index) => (
        <Controller
          key={field.id}
          name={`foo.${fooIndex}.bar.${index}`}
          control={control}
          defaultValue={String(field.value)} // Convert to string
          render={({field}) => (
            <input {...field} value={String(field.value)} /> // Ensure the value is a string
          )}
        />
      ))}
    </div>
  )
};


export default function App() {
  const { control, handleSubmit, getValues } = useForm({
    defaultValues
  });
  const [formValue, setFormValue] = useState(null);

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'foo',
    keyName: 'id'
  });

  const onSubmit = async () => {

  };

  const handlePrintForm = () => {
    setFormValue(getValues())
  };

  return (
    <div className="App">
      <div>
        <h1>Form value:</h1>
        <p>{JSON.stringify(formValue, null, 2)}</p>
      </div>
      <form onSubmit={handleSubmit(onSubmit)}>
        {fields.map((field, index) => (
          <div key={field.id}>
            <FooComponent fooIndex={index} control={control}/>
          </div>
        ))}
        <button onClick={(()=>append({'bar': [1, 2, 3]}))}>Add Foo</button>
      </form>
      <button onClick={handlePrintForm}>Print form</button>
    </div>
  );
}

When there is an array with zero-values in it, the form displays the number of zeros in the array, fewer than it should. For instance, with an array of all zeros nothing gets shown:

zero values not rendereing

Whereas an array with [0, 1, 2]:

enter image description here

How can I fix this?

codesandbox link

2

Answers


  1. I made the following change to the code

    1. Using an array of objects instead of just primitives.
    2. Removing the String coercion and replacing JSON.stringify with the toString() method.

    This should work now.

    import "./styles.css";
    import { useForm, useFieldArray, Control, Controller } from "react-hook-form";
    import { useState } from "react";
    
    const defaultValues = {
      foo: [
        {
          bar: [{ data: 0 }, { data: 0 }, { data: 0 }],
        },
        {
          bar: [{ data: 4 }, { data: 5 }, { data: 6 }],
        },
      ],
    };
    
    const FooComponent = ({ fooIndex, control }) => {
      const { fields, append, remove } = useFieldArray({
        control,
        name: `foo.${fooIndex}.bar`,
        keyName: "id",
      });
    
      return (
        <div>
          <h1>Foo</h1>
          {fields.map((field, index) => (
            <Controller
              key={field.id}
              name={`foo.${fooIndex}.bar.${index}.data`}
              control={control}
              defaultValue={field.data}
              render={({ field }) => (
                <input {...field} value={field.value.toString()} /> // Ensure the value is a string
              )}
            />
          ))}
        </div>
      );
    };
    
    export default function App() {
      const { control, handleSubmit, getValues } = useForm({
        defaultValues,
        shouldUnregister: false,
      });
      const [formValue, setFormValue] = useState(null);
    
      const { fields, append, remove } = useFieldArray({
        control,
        name: "foo",
        keyName: "id",
      });
    
      const onSubmit = async () => {};
    
      const handlePrintForm = () => {
        setFormValue(getValues());
      };
    
      return (
        <div className="App">
          <div>
            <h1>Form value:</h1>
            <p>{JSON.stringify(formValue, null, 2)}</p>
          </div>
          <form onSubmit={handleSubmit(onSubmit)}>
            {fields.map((field, index) => (
              <div key={field.id}>
                <FooComponent fooIndex={index} control={control} />
              </div>
            ))}
            <button onClick={() => append({ bar: [1, 2, 3] })}>Add Foo</button>
          </form>
          <button onClick={handlePrintForm}>Print form</button>
        </div>
      );
    }
    
    Login or Signup to reply.
  2. The solution iniubong Obonguko has provided is more or less correct, as field array must be an array of objects for useFieldArray to function properly.

    You can verify this in their official docs by checking the type of fields and parameter types for all array-related functions (see useFieldArray > Return > fields):
    Definition of type of fields

    A couple of minor corrections to the above solution though:

    • there’s no need to remove string coercion you can keep using String(field.value).
    • append({ bar: [1, 2, 3] }) should be updated to append({ bar: [{ field: 1 }, { field: 2 }, { field: 3 }] }) (you can use whatever name instead of field).
    • you can remove keyName as it will be removed in the next major version (see useFieldArray > Props > keyName).

    Working CodeSandbox: https://codesandbox.io/p/sandbox/happy-bouman-8m8sjm?file=%2Fsrc%2FApp.js%3A1%2C1-81%2C1

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search