Home > Back-end >  react-hook-form stops working when put into a .map() - TypeError: Cannot read properties of undefine
react-hook-form stops working when put into a .map() - TypeError: Cannot read properties of undefine

Time:09-10

Probably being stupid here but I'm struggling to get this working. I'm trying to create a form based off an object. Everything is fine when I'm not using a loop, but when I do, it stops working.

Checkbox.tsx

const Checkbox = React.forwardRef(
    ({ label, name, value, onChange, defaultChecked, ...rest }:any, forwardedRef:any) => {
      const [checked, setChecked] = React.useState(defaultChecked);
  
      React.useEffect(() => {
        if (onChange) {
          onChange(checked);
        }
        console.log(checked, 'checked')
        console.log(name, 'name')
      }, [checked]);
  
      return (
        <div onClick={() => setChecked(!checked)} style={{ cursor: "pointer" }}>
          <input
            style={{ display: "none" }}
            ref={forwardedRef}
            type="checkbox"
            name={name}
            value={value}
            checked={checked}
            onChange={e => {
              setChecked(e.target.checked);
            }}
          />
          [{checked ? "X" : " "}]{label}
        </div>
      );
    }
  );

My Test() parent component:

export default function Test() {
  const onSubmit = (data:any) => {
    alert(JSON.stringify(data));
  };

  const { handleSubmit, register, errors } = useForm();

  return (
    <div className="App">
      <form onSubmit={handleSubmit(onSubmit)}>
        <Checkbox
          ref={register({ required: "This is required" })}
          name="styles"
          value={"tops"}
          label=" tops"
        />
        <Checkbox
          ref={register({ required: "This is required" })}
          name="styles"
          value={"bottoms"}
          label="bottoms"
        />
        {errors.styles && <p className="error">{errors.styles.message}</p>}
        <h2>Next section</h2>
        <Checkbox
          ref={register({ required: "Please select a colour" })}
          name="colors"
          value={"red"}
          label=" Color (custom checkbox)"
        />
        <Checkbox
          ref={register({ required: "Please select a colour" })}
          name="colors"
          value={"blue"}
          label=" Color (custom checkbox)"
        />
        {errors.colors && <p className="error">{errors.colors.message}</p>}
        <button type="submit">submit</button> 
      </form>

    </div>
  );
}

Everything works properly here, my data is structured as follows on submit:

{"styles":["bottoms","tops"],"colors":["blue"]}

My problem arises when I try to build out the form from an object, like so:

'../content/formStyle'

const girl = [
    {
        name: 'styles',
        checkboxes: [
                {
                    value: 'dresses',
                    label: 'Dresses',
                },
                {
                    value: 'pants',
                    label: 'Pants',
                },
                {
                    value: 'skirts',
                    label: 'Skirts',
                },
                {
                    value: 'shorts',
                    label: 'Shorts',
                }
        ]
    },
    {
        name: 'colors',
        checkboxes: [
            {
                value: 'coral',
                label: 'Coral'
            },
            {
                value: 'lime',
                label: 'Lime'
            },
            {
                value: 'mint',
                label: 'Mint'
            },
            {
                value: 'raspberry',
                label: 'Raspberry'
            },
            {
                value: 'red',
                label: 'Red'
            },
            {
                value: 'purple',
                label: 'Purple',
            },
            {
                value: 'teal',
                label: 'Teal'
            },
            {
                value: 'blue',
                label: 'Blue'
            },
            {
                value: 'pink',
                label: 'Pink'
            }
        ]
    }
]

export { girl }

And the component changes to:

import {girl} from '../content/formStyle'

export default function Test() {
  const onSubmit = (data:any) => {
    alert(JSON.stringify(data));
  };

  const { handleSubmit, register, errors } = useForm();

  return (
    <div className="App">
      <form onSubmit={handleSubmit(onSubmit)}>
      {girl?.map((section, index:any) =>{
        return (
          <>
            <h2 className="font-bold">{section.name}</h2>
            {section.checkboxes.map(checkbox => {
              return (
                <Checkbox
                  ref={register({ required: "This is required" })}
                  name={checkbox.value}
                  value={checkbox.value}
                  label={checkbox.label}
                />
              )
            })}
          </>
        )
      })}
        <button type="submit">submit</button> 
      </form>

    </div>
  );
}

Then I get the error:

react-hook-form.es.js?5302:429 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'ref')
    at validateField (react-hook-form.es.js?5302:429:43)
    at eval (react-hook-form.es.js?5302:1199:1)

What am I doing wrong?

CodePudding user response:

Changing the register part of <Checkbox /> fixed it:

              <Checkbox
                key={index}
                ref={register()}
                name={checkbox.value}
                value={true}
                label={checkbox.label}
              />

CodePudding user response:

I feel like react-hook-form makes things much harder whereas you could write your own implementation of FormData. I'm not able to explain why your code is not working, however I can propose an alternative explained easily here:

import React, {useRef} from 'react';

function App() {
    const form = useRef<HTMLFormElement>(null)

    const submit = (e:any) => {
        e.preventDefault();
        const data = new FormData(form.current!);
        const name = data.get("name");
        console.log("name =", name);
    }

    return (
        <form ref={form} onSubmit={submit}>
            <label>
                <input type="checkbox" name="name" />
                <span>TRUC</span>
            </label>
            <button type="submit">Envoyer</button>
        </form>
    );
}

export default App;

Just like react-hook-form the goal here is to avoid countless states controlling the inputs. The FormData object will collect the values and create an object according to the names.

Note that 'on' is used as value for a checkbox, not a boolean.

NOTE: printing data in the console will show an empty object but the values are here.

  • Related