shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
69.13k stars 4.1k forks source link

[bug]: checkbox value not submitted in external form #4537

Open lukaszsamson opened 1 month ago

lukaszsamson commented 1 month ago

Describe the bug

When form is passed as an attribute, checkbox does not contribute value to submitted data

const onSubmit = (e: FormEvent) => {
    e.preventDefault();
    const payload = Object.fromEntries(
      new FormData(e.target as HTMLFormElement).entries(),
    )
    // no foo key in payload
}

<form id={props.formId} onSubmit={onSubmit}><button>Submit</button></form>
<Checkbox
            name="foo"
            form={props.formId}
/>

The reason is Checkbox is implemented as button and does not have a correctly behaving HTML input element

Related issue https://github.com/shadcn-ui/ui/issues/733

Affected component/components

Checkbox

How to reproduce

  1. Create a component with form as described
  2. Check the checkbox
  3. Submit the form
  4. Observe FormData entries

Codesandbox/StackBlitz link

No response

Logs

No response

System Info

@radix-ui/react-checkbox: 1.1.1

Before submitting

lukaszsamson commented 1 month ago

Related to https://github.com/radix-ui/primitives/issues/2530

Usman-T commented 1 month ago

Hey @lukaszsamson, your issue is that the checkbox has the form attribute set to the id of the form yet still does not belong to it. You say that it is because of it being a <button /> instead of a <input />. The form attribute works with both in HTML. You can very well use it with a button, for example:

<form action="/action_page.php" method="get" id="form1">
    <label for="fname">First name:</label>
    <input type="text" id="fname" name="fname"><br><br>
    <label for="lname">Last name:</label>
    <input type="text" id="lname" name="lname">
</form>

<button type="submit" form="form1" value="Submit">Submit</button>

The real reason that it doesn't work is that when a native checkbox is created, the browser automatically includes it in the FormData when it is submitted but when we use a non-native element like the ones from radix-ui.

The @radix-ui/react-checkbox component is not a native input element. It's implemented as a button that visually behaves like a checkbox but doesn't naturally interact with the form data submission process. Because it's a button element:

My Solution

If you really wanna add it to the FormData, you're better of using a simple state for it like

const [isChecked, setIsChecked] = useState(false);

 const onSubmit = (e: FormEvent) => {
  e.preventDefault();
  const formData = new FormData(e.target as HTMLFormElement);

  formData.append('foo', isChecked ? 'true' : 'false');

  const payload = Object.fromEntries(formData.entries());

  console.log(payload); 
};

and the checkbox becomes

 <Checkboxn
    name="foo"
    checked={isChecked}
    onCheckedChange={setIsChecked} />

I hope this helps!

lukaszsamson commented 1 month ago

Sure I can hack a workaround like that or modify the component to render an invisible controlled checkbox input. My point is such basic functionality should work without resorting to hacks