adobe / react-spectrum

A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
https://react-spectrum.adobe.com
Apache License 2.0
12.72k stars 1.09k forks source link

Uncontrolled RAC TextField does not restore value on browser tab restoration #6601

Closed SLTKA closed 2 months ago

SLTKA commented 3 months ago

Provide a general summary of the issue here

This is to extend on top of https://github.com/adobe/react-spectrum/issues/5504.

One particular use case for uncontrolled components we have is to be able to restore value after accidental browser tab closure.

This behavior is hard to custom code with things like session storage as it would cause another tab picking up values from current. Perhaps you can suggest meaningful workaround if not fix.

Basic description: Uncontrolled Input field values are not restored when browser tab is accidentally closed and reopened (restored). Native uncontrolled input fields do this well in tested browsers.

๐Ÿค” Expected Behavior?

Uncontrolled React input values must restore when browser's tab restored. OnChange should fire as well

๐Ÿ˜ฏ Current Behavior

Values not restored, no event fired

๐Ÿ’ Possible Solution

Listen for onChange + check current value on hydration and update state

๐Ÿ”ฆ Context

Use case description: When you have SSR React components rendered and a huge form partially filled in with values user may accidentally close tab when switching to other tabs to get values for a form. When you undo this with hot keys or browser menu (Ctrl+Shift+T on Windows) tab reopens all field values restored by browser automatically and onChange event fired. This is with native fields, so we can restore value in our data stores (by listening onChange) and keep user going.

With React Aria Components it is not working well :(

๐Ÿ–ฅ๏ธ Steps to Reproduce

Here is example I extended from the example in issue above:

https://codesandbox.io/p/devbox/reactariacomponentscontrolled-yjsfwh Added:

<Form>
        <h2>Native Input</h2>
        <label>
          First name:
          <input
            name="firstName"
            defaultValue={defaultFirstName}
            onChange={(e) => {
              console.log("native first name onChange", e);
            }}
          />
        </label>
        <label>
          Last name:
          <input
            name="lastName"
            defaultValue={defaultFirstName}
            onChange={(e) => {
              console.log("native last name onChange", e);
            }}
          />
        </label>
        <div className="button-group">
          This native reset is will reset the values to their defaultValues, as
          expected
          <input type="reset" value="Native Reset" />
        </div>
      </Form>

To test this open running code in a separate tab (outside of code editor), change values in all fields, close tab, restore tab. Only "native" block will be properly restored and only "native" logs will be shown in console.

Tested on Chrome and Firefox on Windows.

Version

react-aria-components 1.2.1

What browsers are you seeing the problem on?

Firefox, Chrome

If other, please specify.

No response

What operating system are you using?

Windows

๐Ÿงข Your Company/Team

No response

๐Ÿ•ท Tracking Issue

No response

snowystinger commented 2 months ago

Hahaha, I thought my browser autofill was interfering for far too long because you used my name in the demo.

This seems like a bit of a security risk. If I was on a public computer filling in a form, anyone could sit down at the computer after me and restore my tab and be able to view anything I'd entered.

I also don't see the restore calling onChange, which means if you have any other state hooked up to it, it won't be reflected. This can lead to an ambiguous UI.

In addition, refreshing the tab doesn't keep the form state.

Browser discrepancies (on Mac):

SLTKA commented 2 months ago

I also don't see the restore calling onChange

It looks like inconsistent across browsers, FF does call it, looks like others do not. Updated example to push logs to DOM for visibility.

This seems like a bit of a security risk

Probably, but I'd leave it to browsers developers. I probably can be surprised with number of people using public devices nowadays, and if they not using Incognito or similar features... We also do not know for how long browsers retain the data, I can't find any meaningful documentation describing the feature on them and if it is part of standard.

On the other hand, all above is not important because this bug ticket is about having ability to render native input and use any browser native features available today or tomorrow, including all plugins like password managers and other tools. The current implementation doesn't allow this because there is code which overwrites values after hydration, this is MOST LIKELY challenging scenarios like: User input value faster than all scripts load with slow connection and then value is replaced with hydrated value (this is just a theory, needs to have more tests and controlled react hydration)

snowystinger commented 2 months ago

I'm unaware of any issues with password managers. I use one myself and we've tested them with our components. Please let us know if you find an issue there.

I also don't understand what you mean about rendering native inputs. All our form components are backed by native inputs.

SLTKA commented 2 months ago

I also don't understand what you mean about rendering native inputs.

I mean that disregard they are native they do not behave like native as I describe above with tab restoration.

I have no answer for password managers, I think they may delay filling to ensure having values after all scripts done. I'll look into it, probably not worth discussing further.

It would be nice to still consider fixing tab restoration behavior of browsers and do not "steal" native behavior from native components. All our discussions about security of this are not helpful because this should be under browsers devs control, as web devs we just build for browsers and following security guidance.

To unpack my previous message even further I created example of typical production situation we often experience with component libraries, imagine the following situation:

Here is example with artificial delay for 10 seconds for hydration so you can enter values for testing. 10 seconds is not far from reality I worked before with some big web sites (let's not dive into reasons or possible bundle optimization discussions as it is not related).

https://codesandbox.io/p/devbox/reactariacomponentscontrolled-yjsfwh

Please open it and update values on fields in the first 10 seconds (added count down for convenience) Both controlled and uncontrolled fields failing the test, Pure native fields obviously hold it well (probably state not matching but it is could be solved)

User level comment (just sentiment, please do not reply on this): it is very annoying when I open another React based online store web page, type something in search bar and hit search, and results show me ate out of my chocolate request. Some people enjoy 5g connection and cool precached web sites. But reality is that connection is lots worse and agile engineering teams are pushing updates every fortnight which rename all files and invalidate all caches in my browsers since it now need to pull all new set of JS and CSS files.

Perhaps, there are ways to fix this. Happy to assist with looking for ideas if there is interest to move forward on this. Unfortunately, you already have this logic for some reasons and my ideas/solutions may affect them. If possible, can you share list of all requirements uncontrolled component should satisfy and quick summary (or links to code) how this resolved. This can help me and other readers to participate in discussion and offer other solutions.

snowystinger commented 2 months ago

I looked into it a little more to see what was actually causing this behavior, it's due to us controlling the value of textfields internally. I don't see a way to not do that and continue to offer features such as validation.

Given that the restore tab works differently in each of the browsers, I would recommend making use of browser local storage to accomplish this if you need it. This way it will work the same across all browsers.

It's regrettable that a slow network / big app might remove the value in the midst of typing. However, if anyone controls any input, this problem will exist. At least until a change event is fired when the old value is restored (this would be a React or browser issue).

Checked other libraries as well: https://mui.com/toolpad/studio/components/text-field/ has a similar problem, the placeholder and old value will compete for space. And if you control the value, it just won't work at all.

Couldn't easily check Chakra UI because their examples are loaded dynamically later. But I imagine they have similar issues.

https://ant.design/components/input ant also controls the state, so won't work there.

I'm going to close this issue as working as intended with an available workaround. Thank you for the feedback.