Chrome Autofill causes textbox collision for Textfield Label and Value
Asked Answered
P

3

5

In React, Autocomplete Chrome values don't trigger onChange event immediately. Thus it causes a collision of MUI TextField Label and actual values, during initial loading of page. How can I resolve this issue ?

enter image description here

Trying numerous methods, InputLabelProps shrink on Value does not work either. https://mcmap.net/q/259024/-react-material-ui-label-overlaps-with-text

<StyledTextField
    fullWidth
    id="email"
    label="Email"
    size="medium"
    value={emailAddress}
    onChange={(e) => setEmailAddress(e.target.value)}
    error={emailError}
    InputLabelProps={{
      shrink: emailAddress != null && emailAddress != "" ? true : false,
    }}
    helperText={emailError ? "Please enter a valid email" : "Required"}
 />

Trying this solution also gives issues: when doing const theme = createTheme({

Github resource: https://github.com/mui/material-ui/issues/14427#issuecomment-466054906

enter image description here

Piane answered 3/8, 2023 at 18:26 Comment(1)
I think you can use onInput in inputProps instead of onChange. But I'm not sure it's working for you.Bova
S
5

Personally, I don't think the juice is worth the squeeze for this vs just disabling shrink or auto-complete for the log-in, but no one asked my opinion, so...

From what I understand, the reason this is more painful than we'd like is because it was originally intended as a security feature to prevent password theft via auto-complete, but once Chrome (et al.) removed the restrictions, React's de-duping mechanism took over. There are some slightly more hack-y work-arounds than what I'm going to suggest, but you can decide which you like.

To side-step this, you can add a handler to each input's onAnimationStart event, check if the animationName is "mui-auto-fill", and then check if the input has a -webkit-autofill pseudo class, to see if the browser has auto-filled the field. You'll also want to handle the "mui-auto-fill-cancel" case for scenarios where the form is auto-filled and the user clears the values (to reset shrink.)

For example:

const [passwordHasValue, setPasswordHasValue] = React.useState(false);

// 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);
    ...
  }}
/>

The result on load should appear as:

example

Update with cancel -- allows user to clear out the form field, after load with auto-fill, and the label is reset:

example with reset field

FYI: I made my makeAnimationStartHandler a function that accepts a React.useState() setter as a param and returns a handler that actually does the work because your example has two fields and I wanted to 1) reduce code and 2) allow you to still handle the manual entry use-case for each field separately, if desired.

Working Example: https://z3vxm7.csb.app/
Working CodeSandbox: https://codesandbox.io/s/autofill-and-mui-label-shrink-z3vxm7?file=/Demo.tsx

Shrewd answered 4/8, 2023 at 5:49 Comment(3)
for some reason its not working, the label always Stays on Top, even when textbox is empty on minePiane
@Piane Ah I missed the cancel animation in my example -- I've updated my answer.Shrewd
@SteveGomez Also there would be usefull to shrink it on focus as it is default behaviour. I am gonna fork it with focus and blur handler. codesandbox.io/s/autofill-and-mui-label-shrink-forked-kd9699Fayfayal
L
2

Thanks for this Steve. Works great. I wrote a component for my project based on your answer:

import {TextField} from "@mui/material";
import {useCallback, useState} from "react";

const AutoFillAwareTextField = ({onChange, inputProps, InputLabelProps, ...rest}) => {

    const [fieldHasValue, setFieldHasValue] = useState(false)
    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);
        }
    }

    const _onChange = useCallback((e) => {
        onChange(e.target.value);
        setFieldHasValue(e.target.value !== "")
    }, [onChange])

    return <TextField
        inputProps={{
            onAnimationStart: makeAnimationStartHandler(setFieldHasValue),
            ...inputProps
        }}
        InputLabelProps={{
            shrink: fieldHasValue,
            ...InputLabelProps
        }}
        onChange={_onChange}
        {...rest}
    />
}
export default AutoFillAwareTextField
Larcener answered 18/8, 2023 at 9:12 Comment(0)
D
0

There is a much simpler way to do this, and if you already have some useEffect hooks, it might be able to slot in with only a few new lines.

  const [myName, setMyName] = useState(myNameCookie) // Your dynamic value
  const [shrinkName, setShrinkName] = useState(false);

  useEffect(() => {
    if (myName.length > 0) {
      setShrinkName(true);
    }
  }, []);

  useEffect(() => {
    setShrinkName(myName.length > 0);
  }, [myName])

  return (
    <TextField
      value={myName}
      label="My Name"
      variant="outlined"
      InputLabelProps={{ shrink: shrinkName }}
      onChange={(e) => setMyName(e.target.value)} />
  )

Now, the boolean value of shrink will be updated on load (due to the empty useEffect) based on the length of the field's value. (Note that you probably want this value to always be a string, not undefined.) And when the value of the field changes, the boolean will be updated.

I happened to already be capturing this due to wanting to dynamically set which input gets focus, so slotting this in was only 4 new lines.

Dactyl answered 20/10 at 2:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.