edmundhung / conform

A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js.
https://conform.guide
MIT License
1.8k stars 101 forks source link

[React 19 / Next.js 15] Form always resets on submit #681

Open heggemsnes opened 2 months ago

heggemsnes commented 2 months ago

Describe the bug and the expected behavior

The way forms are handled in React 19 resets the form: https://github.com/facebook/react/issues/29034.

Using the lastResult in the useForm hook should update the default values again with the result when using submission.reply() but this doesn't seem to work.

Conform version

1.1.4

Steps to Reproduce the Bug or Issue

  1. Fill out form and submit
  2. Values are cleared even though lastResult contains the values

Check reproduction here:

https://codesandbox.io/p/devbox/cocky-shannon-yjk6tk?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clxi4r5fg00063b6jp5yxj36m%2522%252C%2522sizes%2522%253A%255B70%252C30%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clxi4r5fg00023b6jujvz5j9o%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clxi4r5fg00043b6jual24qnb%2522%257D%252C%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clxi4rjww004b3b6j044z1a21%2522%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clxi4r5fg00053b6jexu1ncex%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B57.29103669427997%252C42.70896330572003%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clxi4r5fg00023b6jujvz5j9o%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clxi4r5fg00013b6j2qbo79l0%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clxi4r5fg00023b6jujvz5j9o%2522%252C%2522activeTabId%2522%253A%2522clxi4r5fg00013b6j2qbo79l0%2522%257D%252C%2522clxi4r5fg00053b6jexu1ncex%2522%253A%257B%2522id%2522%253A%2522clxi4r5fg00053b6jexu1ncex%2522%252C%2522activeTabId%2522%253A%2522clxi4rfaq002m3b6j45q5dp21%2522%252C%2522tabs%2522%253A%255B%257B%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A3000%252C%2522id%2522%253A%2522clxi4rfaq002m3b6j45q5dp21%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522path%2522%253A%2522%252F%2522%257D%255D%257D%252C%2522clxi4r5fg00043b6jual24qnb%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clxi4r5fg00033b6jlmjhzb35%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%257D%255D%252C%2522id%2522%253A%2522clxi4r5fg00043b6jual24qnb%2522%252C%2522activeTabId%2522%253A%2522clxi4r5fg00033b6jlmjhzb35%2522%257D%252C%2522clxi4rjww004b3b6j044z1a21%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clxi4rg6400473b6jepx3qv0j%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TERMINAL%2522%252C%2522shellId%2522%253A%2522clxi4rjzn000fdcdtgbzlabzc%2522%257D%255D%252C%2522id%2522%253A%2522clxi4rjww004b3b6j044z1a21%2522%252C%2522activeTabId%2522%253A%2522clxi4rg6400473b6jepx3qv0j%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D

What browsers are you seeing the problem on?

Chrome

Screenshots or Videos

No response

Additional context

No response

edmundhung commented 2 months ago

Thank you for getting me a repo! So the issue here is that Conform supports resetting the form with a reset button (<button type="reset" />) and it will clear all the state whenever a reset event is dispatched, including errors and previous submitted value and so breaks the progressive enhancement setting 😅

To fix this, We will need to stop listening to the reset event and drop support of native reset button. But this would be a breaking change that we are not ready to make yet.

Here is a temporary solution. You need to include this next to where you call the useForm() hook.

useEffect(() => {
    const preventDefault = (event: Event) => {
      // Make sure the reset event is dispatched on the corresponding form element
      if (event.target === document.forms.namedItem(form.id)) {
        // Tell Conform to ignore the form reset event
        event.preventDefault();
      }
    };

    document.addEventListener("reset", preventDefault, true);

    return () => {
      document.removeEventListener("reset", preventDefault, true);
    };
}, [form.id]);
heggemsnes commented 2 months ago

Thanks for the fix and quick reply!

This is a bit over my head, why is the reset dispatched on submit?

Perhaps as a temporary patch we could add running this effect as an option to the useForm hooks :)

damian-kociszewski commented 1 month ago

I spent a lot of time on this 😛, the least we could do here is add a hint to the Next.js integration docs.