tailwindlabs / headlessui

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
https://headlessui.com
MIT License
25.02k stars 1.03k forks source link

Uncontrolled `Switch` `checked` state not visible in DOM #3317

Closed aaronadamsCA closed 1 week ago

aaronadamsCA commented 1 week ago

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

v2.1.0

What browser are you using?

Chromium

Reproduction URL

https://codesandbox.io/p/devbox/headlessui-switch-no-input-qvyqvx?file=%2Fsrc%2FApp.jsx

Describe your issue

Edit: Modifying the below now. The change to checked isn't visible in the DOM, but the submitted form data is unaffected. So this is a much more minor issue than I thought.


I'm currently attempting to upgrade from v1 to v2, and we've run into trouble with our uncontrolled Switch components. It seems v2 is not keeping the value of the hidden input in sync like v1 did.

I've attached my best shot at a minimal reproduction, let me know if I'm doing something wrong.

(Side note, I don't know why v2 moves the hidden input to a weird hidden div; this had me thinking for at least an hour that the hidden input had simply disappeared. I liked it better when it was a simple sibling!)

aaronadamsCA commented 1 week ago

Not sure I understand what's going on here, but the form itself seems unaffected by the fact the dev tools don't show the DOM update. Closing for now until I get a better grasp on the problem I'm seeing.

RobinMalfait commented 1 week ago

Hey!

My guess is that the devtools aren't reflecting the change because it is hidden but as you discovered already the value should still be synced correctly. I created a small reproduction that shows all the fields of the form to debug it: https://codesandbox.io/p/devbox/headlessui-switch-no-input-forked-ctlftn?file=%2Fsrc%2FApp.jsx%3A11%2C53

Note that the switch behaves as a native checkbox by default, which means that the value will be 'on' when checked but the field will not be present when it is not checked.

To answer your second question, we moved the hidden inputs into a hidden div for a few reasons:

  1. If we have more hidden inputs (e.g.: when serializing objects) then everything will be nested in a single element which is a bit easier because you can hide the children in devtools and not worry about n DOM nodes.
  2. A more important reason is styling. Let's say you want to style a label and the control using a sibling selector such as + or ~ then the hidden input that was rendered before the actual control will make it harder to style and you have to know whether it's there or not (because it will be conditionally rendered based on whether you added the name prop). We can't move it right after the control either because that could mess with styles between the control and the description. Here is a link with some examples: https://play.tailwindcss.com/AwIco119IM

Hope this helps!

aaronadamsCA commented 1 week ago

Thank you @RobinMalfait, this all makes perfect sense to me. Appreciate it!