I'm creating a form for a user to input their address for the purposes of collecting a billing address for payment information. We only serve customers within the United States so there is an autocomplete mUI component with a dropdown menu so that the user can select which state they live in. Here is the code we are using for that:
export const stateNamesAndAbbreviations: ReadonlyArray<StateNameAbbreviation> = [
{ name: "Alabama", abbreviation: "AL" },
{ name: "Alaska", abbreviation: "AK" },
{ name: "Arizona", abbreviation: "AZ" },
// Shortened for brevity. . .
]
const StateDropdown: React.FC<StateDropDownProps> = ({state, fieldUpdated}) =>
<Stack width={"100%"}>
<InputLabel shrink>State</InputLabel>
<Autocomplete size="small"
autoSelect
autoHighlight
selectOnFocus
options={stateNamesAndAbbreviations.map((x) => x.abbreviation)}
data-testid={NewPaymentMethodTestHandles.State}
onChange={(event, newValue) => fieldUpdated(PaymentMethodFields.state, newValue!)}
renderInput={(params) => (
<TextField variant="outlined"
{...params}
error={state.isDirty && !state.isValid}
helperText={state.isDirty && !state.isValid ? "State is required" : null}
data-testid={NewPaymentMethodTestHandles.StateInput}
onChange={(event) => fieldUpdated(PaymentMethodFields.state, event.currentTarget.value)}
onBlur={() => !state.isDirty && fieldUpdated(PaymentMethodFields.state, state.value)}
inputProps={{
...params.inputProps,
autoComplete: "address-level1",
}}
/>
)}
/>
</Stack>
Edit: A code sandbox is available here. It's stripped down but has the same problem: if you select the "Name" box and autofill an address saved in your browser the state dropdown menu opens rather than being auto filled.
The dropdown works fine in tandem with the rest of the form which we use to collect first name, last name etc. The issue is when it comes to the browser autofilling a saved address. All of the fields will be populated properly except the state dropdown component, which for whatever reason simply opens the dropdown menu instead of being populated with the state name. I'm still able to properly submit the form by manually selecting a state, and even have the browser save the address I inputted, but autofill does not seem to work on this field. I verified the state is indeed attempting to go to the correct field, since if I remove the dropdown functionality but keep the textfield intact the state gets autofilled correctly. Additionally, when Chrome brings up the menu to autofill the information hovering on it to to preview the info it is about to autofill, the state will be previewed in the correct place.
Any help is much appreciated.
Ok so i'm not an expert in react nor am I an expert in mUI, so pardon me if my code is not according to those libs' best practices. The way I see it, I think you just need to add a handler to the autocomplete's <TextField>
when there is a value change (whether it's from autofills or it's from user input).
Before showing my approach, I actually don't know how you manage the states for the inputs, so I will just add the states according to what I think are needed. So my approach is actually just to bind the value of the autocomplete to a state with value={value}
, then in the <TextField>
, I add onChange
listener which listens to input change (normally it's only triggered by user change, but at least in my browser, it's triggered by autofill change as well). Inside the listener, it will try to check if the inputted string exists in the abbreviation mapping stateNamesAndAbbreviations
, if it does exist, it immediately update the state value
to the the abbreviation of the inputted string, if it doesn't exist, it will not do anything.
Here's the modified codesandbox, and here's the modified dropdown component:
const StateDropdown = ({ state, fieldUpdated }) => {
const [value, setValue] = React.useState(null);
const [open, setOpen] = React.useState(false);
const closeDropdown = () => setOpen(false);
const openDropdown = () => setOpen(true);
const dropdownInput = React.useRef(null);
return (
<Stack width={"100%"}>
<InputLabel shrink>State</InputLabel>
<Autocomplete
open={open}
onOpen={openDropdown}
onClose={closeDropdown}
value={value}
onChange={(event, newValue) => {
setValue(newValue || null);
}}
size="small"
autoSelect
autoHighlight
selectOnFocus
options={stateNamesAndAbbreviations.map((x) => x.abbreviation)}
renderInput={(params) => (
<TextField
inputRef={dropdownInput}
onChange={(event) => {
const newValue = event.target.value;
const existingState = stateNamesAndAbbreviations.find(
(state) => state.name === newValue
);
// only force close dropdown when input is updated but is not on focus
if (document.activeElement !== dropdownInput.current) {
if (!!existingState) {
setValue(existingState.abbreviation);
}
closeDropdown();
}
}}
variant="outlined"
{...params}
inputProps={{
...params.inputProps,
autoComplete: "address-level1"
}}
/>
)}
/>
</Stack>
);
};
There is a potential problem that I could think of though. Suppose we have the following mapping:
[
{ name: "Indiana", abbreviation: "IN" },
{ name: "Indianajones", abbreviation: "IJ" },
]
When, for instance, a user is trying to input Indianajones
, the dropdown will immediately change the input and select IN
instead of allowing the user to continue typing. Despite that, it seems that this wouldn't happen here, since your mapping doesn't have any name which is a prefix of another name, so as long as the mapping is the one you provided, this won't happen.
I solved the potential problem by only update the dropdown value when the dropdown input is not being focused, so whenever the dropdown value is updated by anything than user input, it will be validated.