htmlcssreactjsgoogle-chromematerial-ui

Chrome Autofill causes textbox collision for Textfield Label and Value


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://stackoverflow.com/a/65070465/15435022

<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


Solution

  • 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