material-ui icon indicating copy to clipboard operation
material-ui copied to clipboard

[TextField] Styling bug when controlled component uses autoFill

Open dwjohnston opened this issue 2 years ago • 17 comments

Duplicates

  • [X] I have searched the existing issues

Latest version

  • [X] I have tested the latest version

Steps to reproduce 🕹

Link to live example:

Steps:

  1. Visit: https://d4yz1f.csb.app/? using a Chrome browser
  2. Fill in the 'CREATE PASSWORD HERE' section, submit the form, and save password in your browsers password manager 3.Reload page - observe the styling issue.

Current behavior 😯

image

Expected behavior 🤔

Should not have that overlap when autofill is present.

Here is the same app using @mui/[email protected] and the issue is not present: https://v1kf03.csb.app/?

Context 🔦

I upgraded from 5.9.3 to 5.11.12 and now my login page looks bad.

Your environment 🌎

See code sandbox.

dwjohnston avatar Mar 07 '23 05:03 dwjohnston

I manually search through Mui versions to find that this bug appears to have been introduced between 5.10.11 and 5.10.12

dwjohnston avatar Mar 07 '23 05:03 dwjohnston

I couldn't reproduce this issue on Firefox, mostly because I'm struggling with it to get the autofill to work.

dwjohnston avatar Mar 07 '23 05:03 dwjohnston

Thanks for reporting this. I was able to reproduce the issue using the Material UI version you specified.

michaldudak avatar Mar 13 '23 11:03 michaldudak

Thanks to this comment https://github.com/facebook/react/issues/1159#issuecomment-1025423604, I temporarily solved the problem and it may be helpful to you.

const [wasInitiallyAutofilled, setWasInitiallyAutofilled] = useState(false)

useLayoutEffect(
    () => {
        /**
         * You can access chrome://flags/#unsafely-treat-insecure-origin-as-secure in your navigation bar
         * and add your domain there for enable autofill in local development
         */
        let autofilled = false
        const checkAutofill = () => {
            if (autofilled) return
            const inputElements = document.getElementsByTagName('input')
            if (!inputElements) return
            for (let i = 0; i < inputElements.length; i++) {
                const input = inputElements[i]
                const isAutofilled = input.matches('*:-webkit-autofill')
                if (isAutofilled) {
                    autofilled = true
                    break
                }
            }
            setWasInitiallyAutofilled(autofilled)
        }
        // The time when it's ready is not very stable, so check few times
        setTimeout(checkAutofill, 500)
        setTimeout(checkAutofill, 1000)
        setTimeout(checkAutofill, 2000)
    }, 
    [],
)

<Form>
    <FormTextField
        name="email"
        focused={wasInitiallyAutofilled ? true : undefined}
        ...
    />
    <FormPassword
        name="password"
        focused={wasInitiallyAutofilled ? true : undefined}
        ...
    />
</Form>

sivanzheng avatar May 22 '23 08:05 sivanzheng

Not great, but a slightly better work-around than above, IMO:

// For re-usability, I made this a function that accepts a useState set function
// and returns a handler for each input to use, since you have at least two 
// TextFields to deal with.
const makeAnimationStartHandler = (stateSetter) => (e) => {
  const autofilled = !!e.target?.matches("*:-webkit-autofill");
  if (e.animationName === "mui-auto-fill") {
    stateSetter(autofilled);
  }

  if (e.animationName === "mui-auto-fill-cancel") {
    stateSetter(autofilled);
  }
};

...

<TextField
  type="password"
  id="password"
  inputProps={{
    onAnimationStart: makeAnimationStartHandler(setPasswordHasValue)
  }}
  InputLabelProps={{
    shrink: passwordHasValue
  }}
  label="Password"
  value={password}
  onChange={(e) => {
    setPassword(e.target.value);
    ...
  }}
/>

https://stackoverflow.com/questions/76830737/chrome-autofill-causes-textbox-collision-for-textfield-label-and-value/76833254#76833254

scarabaeus avatar Aug 04 '23 05:08 scarabaeus

Hi guys. I am new to this, but what I did find when I was busy with a form is that if there is a specific input that needs to be evaluated even when auto filled, and you can't find any solution yet, useEffect can help. When that specific value changes, whether filled or auto filled, it will get triggered.

ErrolFrancois avatar Aug 08 '23 09:08 ErrolFrancois

This is happening to me and the @scarabaeus solution did not work unfortunately, but clearly, we have this bug in MUI 5 in our project, for those who have the auto-fill activated and only access the page that already fills all available fields.

Ruffeng avatar Jan 17 '24 17:01 Ruffeng

Hi Ruffeng. I am not sure if you found any other solution yet... Check this out and let me know if this helps. You can also add a useEffect, so that when the page loads, the useEffect will trigger "updateInputClass()" without any prop. During updating your form, you can pass the input name on an 'onBlur' event. Here, i am using react hook forms that give access to errors and touchedFields. But you can propbably tweek it for your use.

` function itterateInputs() { const inputs = document.querySelectorAll("input"); inputs.forEach((input) => { input.value.length > 0 ? input.classList.add("filled-input") : input.classList.remove("filled-input"); }); }

function updateInputClass(inputName) { const input = inputName && document.getElementsByName(inputName)[0]; if (inputName) { if (!errors[inputName] && touchedFields[inputName]) { input.classList.add("filled-input"); } else if (errors[inputName]) { input.classList.remove("filled-input"); input.classList.add("error-input"); } else { input.classList.remove("filled-input"); } } else { itterateInputs(); } } `

ErrolFrancois avatar Feb 08 '24 13:02 ErrolFrancois

You can also remove the error part in the code that I sent. I have found that the following code has been working for me, to add the "error-input" class to the input. This is a custom input, so just check the className: <FormInput {...formInputs[inputName]} register={register} onChange={handleInputChange} className={ errors[inputName] ? "error-input" : !errors[inputName] && touchedFields[inputName] ? "filled-input" : "" } errorMessage={errors[inputName]?.message} />

ErrolFrancois avatar Feb 08 '24 15:02 ErrolFrancois

Is there an ETA on this , we are facing the same issue and will be a huge timesaver for us to have this fixed.

srikant-thrive avatar Jul 12 '24 14:07 srikant-thrive

@DiegoAndai, I'm assigning this to you so you can prioritize it.

michaldudak avatar Jul 15 '24 09:07 michaldudak

Looked into this issue today.

Bug explanation

There are two things that together cause the behavior:

Because said effect runs after onFilled, and it reads the mismatched controlled value (which is empty), filled is incorrectly reset to false.

Solutions

There's an immediate solution which is this change:

Click to toggle change
diff --git a/packages/mui-material/src/FormControl/FormControl.js b/packages/mui-material/src/FormControl/FormControl.js
index e9d5d01b77..590e7eaf18 100644
--- a/packages/mui-material/src/FormControl/FormControl.js
+++ b/packages/mui-material/src/FormControl/FormControl.js
@@ -192,6 +192,14 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) {
     };
   }
 
+  const onFilled = React.useCallback(() => {
+    setFilled(true);
+  }, []);
+
+  const onEmpty = React.useCallback(() => {
+    setFilled(false);
+  }, []);
+
   const childContext = React.useMemo(() => {
     return {
       adornedStart,
@@ -207,12 +215,8 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) {
       onBlur: () => {
         setFocused(false);
       },
-      onEmpty: () => {
-        setFilled(false);
-      },
-      onFilled: () => {
-        setFilled(true);
-      },
+      onEmpty,
+      onFilled,
       onFocus: () => {
         setFocused(true);
       },
@@ -229,6 +233,8 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) {
     focused,
     fullWidth,
     hiddenLabel,
+    onEmpty,
+    onFilled,
     registerEffect,
     required,
     size,

But I think the correct solution will be to eventually refactor the autofill style implementation to rely on the :autofill CSS selector.

So I propose going for the temporal solution and creating an issue to implement this refactor in the future. What do you think @aarongarciah?

Other

A note on the repro

After user interaction the text field value shows as [object Object] because the onChange callback is incorrect:

 <TextField
     value={value}
-    onChange={setValue}
+    onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+        setValue(event.target.value);
+   }}
     autoComplete="username"
     id="outlined-basic"
     label="username"
     variant="outlined"
 />

Thanks for the help

Thanks @dwjohnston for pinning the exact version on which this was introduced, it was really helpful 👌🏼.

DiegoAndai avatar Jul 17 '24 17:07 DiegoAndai

@DiegoAndai makes sense, the temporal solution is an improvement already.

aarongarciah avatar Jul 18 '24 21:07 aarongarciah

Any updates on this ?

srikant-thrive avatar Sep 24 '24 10:09 srikant-thrive

any information?

AndreyVerkhusha avatar Oct 10 '24 13:10 AndreyVerkhusha

Sorry for the delay, I'll try to get a PR for this next week.

DiegoAndai avatar Oct 11 '24 20:10 DiegoAndai

For the people experiencing this issue, may I ask you to test with https://github.com/mui/material-ui/pull/44135's build and check if the issue is fixed? You can do so by doing the following on your project's package.json:

"@mui/material": "https://pkg.csb.dev/mui/material-ui/commit/6b32256d/@mui/material",

DiegoAndai avatar Oct 16 '24 20:10 DiegoAndai

For the people experiencing this issue, may I ask you to test with #44135 build and check if the issue is fixed? You can do so by doing the following on your project's package.json:

"@mui/material": "https://pkg.csb.dev/mui/material-ui/commit/6b32256d/@mui/material",

For me, it solves the issue. However, the input field's value only updates when the Chrome tab is in focus. This means the textField's value is not set until the user clicks on the website.

KostyalBalint avatar Jan 03 '25 10:01 KostyalBalint

This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue. Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

[!NOTE] @dwjohnston How did we do? Your experience with our support team matters to us. If you have a moment, please share your thoughts in this short Support Satisfaction survey.

github-actions[bot] avatar Jan 20 '25 21:01 github-actions[bot]

The fix has been merged and will be out in the next release (>6.4.0)

This means the textField's value is not set until the user clicks on the website.

We tried to fix this in https://github.com/mui/material-ui/pull/44135 but ultimately it seems to be a limitation on Chrome in which there's no way of accessing the value before user interaction (see https://github.com/mui/material-ui/pull/44135#issuecomment-2593134886).

If anyone figures out a way to improve this, I'll gladly review it 😊

DiegoAndai avatar Jan 20 '25 21:01 DiegoAndai