Default value of Autocomplete is undefined in MUI v5 - javascript

I have set the defaultValue prop in Autocomplete component of MUI v5 but the value is always undefined.
Excerpt from my code
const cars = [
{ label: "BMW", code: "Volkswagen" },
{ label: "Benz", code: "Mercedes" }
];
...
const formik = useFormik({
initialValues: {
car: ""
},
onSubmit: (values) => {}
});
...
<Autocomplete
id="autocomplete"
options={cars}
getOptionLabel={(option) => `${option.code} - ${option.label}`}
renderOption={(props, option) => (
<Box component="li" {...props}>
{option.code} - {option.label}
</Box>
)}
defaultValue={`${cars[0].code} - ${cars[0].label}`}
onChange={(e, value) => {
formik.setFieldValue("car", `${value.code} - ${value.name}`);
}}
renderInput={(params) => (
<TextField
{...params}
id="textField"
name="cars"
label="Cars"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
inputProps={{
...params.inputProps,
autoComplete: "new-password"
}}
/>
)}
/>
I created a working example using CodeSandbox. Could anyone please help?

you can just use:
defaultValue={cars[0]}

You got undefined-undefined because you have already described how the selected property should look like here:
getOptionLabel={(option) => `${option.code} - ${option.label}`}
Therefore, you should pass to default option an object where it's possible to access code and label values.
Changing defaultValue={cars[0]} will help.
Working Demo

The issue is in
getOptionLabel prop you were not checking wheather *
option?.code && option?.label
is defined or not so because of this undefined was showing. so change line 23 of your code to
getOptionLabel={(option) =>option.code && option.label ?
${option.code} - ${option.label} : ''}

Related

How can I prevent mUI from showing dropdown menu after Chrome autofills a user's address?

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.
This is quite simple you just need to see when autocomplete input of AutoComplete
const StateDropdown: React.FC<StateDropDownProps> = ({state, fieldUpdated}) => {
const [value, setValue] = useState('')
const [open, setOpen] = useState(false)
return (
<Stack width={"100%"}>
<InputLabel shrink>State</InputLabel>
<Autocomplete size="small"
autoSelect
autoHighlight
selectOnFocus
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
value={value}
onChange={(event, value) => setValue(value)}
onInputChange={(event, value) => {
if (!stateNamesAndAbbreviations.find(item => item.abbreviation === value)) return
setValue(value)
setOpen(true)
setTimeout(() => setOpen(false)) // because on task end
}}
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>
)
}
here is the example i solved your sandbox: https://codesandbox.io/s/practical-kapitsa-ok15r6

How to clear data on Select multiple when typing on another textfield using reactjs

This is my current code, what I want here is after selecting on Select option (CHIP) and if the user type on the textfield I want to clear what the user selected on CHIP, What should I do to get what i want functionality?
const names = [
'Oliver Hansen',
'Van Henry',
'April Tucker',
];
function getStyles(name, personName, theme) {
return {
fontWeight:
personName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
export default function MultipleSelectChip() {
const theme = useTheme();
const [personName, setPersonName] = React.useState([]);
const handleChange = (event) => {
const {
target: { value },
} = event;
setPersonName(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
);
};
const handleChangeTextField = (event) => {
setPersonName(null);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
labelId="demo-multiple-chip-label"
id="demo-multiple-chip"
multiple
value={personName}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{names.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
<TextField
variant="outlined"
label="Type anything to remove the value of Chip"
onChange={handleChangeTextField} />
</FormControl>
</div>
This is my current code, what I want here is after selecting on Select option (CHIP) and if the user type on the textfield I want to clear what the user selected on CHIP, What should I do to get what i want functionality?
I would set your textfield to be controlled (ie backed by a state variable) and add an effect hook to watch it.
When it receives a value, clear the selected names by setting personNames back to an empty array.
const [text, setText] = useState("");
useEffect(() => {
if (text) {
setPersonName([]);
}
}, [text]);
const handleChangeTextField = ({ target: { value } }) => {
setText(value);
};
<TextField
value={text}
variant="outlined"
label="Type anything to remove the value of Chip"
onChange={handleChangeTextField}
/>
You might also want to clear the text field when selecting names by adding this into handleChange...
setText("");

Material UI - How to remove focus on autocomplete once an option is selected?

I am using an autocomplete component for my search. It works well for desktop but on mobile, the keyboard remains open once an option has been selected even though it redirects to another page.
<Autocomplete
id="free-solo-demo"
value={""}
className={classes.auto}
onChange={(event, value) => searchFunc(value)}
options={items.map((item) => item.item)}
renderInput={(params) => (
<TextField
label="Search..."
{...params}
InputProps={{
...params.InputProps,
disableUnderline: true,
classes: { input: classes.input },
}}
value={search}
/>
)}
/>
Search function:
const searchFunc = (value) => {
if (value) {
const param = items.filter((item) => item.item === value)[0].id;
setSearch("");
history.push(`/details/${param}`);
}
};
I want the mobile keyboard to close once an item has been selected!! Thank you :)

Disable backspace deleting of options in Autocomplete

I want to use an Autocomplete (from the Material-Ui library) component from material ui to select multiple options, and those options should not be able to be removed (directly) by the user.
The problem I'm facing is that the user can delete the options from the Autocomplete if they focus the component and press backspace as if they are deleting text.
Code
This is the component I'm using:
<Autocomplete multiple
options={options}
getOptionLabel={option => option.title}
renderInput={params =>
<TextField {...params} label="Autocomplete" variant="outlined" />
}
onChange={this.onAutocompleteChange.bind(this)}
getOptionSelected={(option: Option, value: Option) => option.value === value.value}
filterSelectedOptions={true}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip key={option.value} label={option.title} color="primary" />
))
}
disableClearable={true}
/>
What I tried
Disabling the TextField from the renderInput prop with disable={true} has no effect.
Adding InputProps={{ disabled: true }} or InputProps={{ readOnly: true }} to TextField disables the Autocomplete completely.
Adding ChipProps={{ disabled: true }} to the Autocomplete has no effect.
Thanks for reading!
To control this aspect, you need to use a controlled approach to the Autocomplete's value as demonstrated in this demo.
In the documentation for the onChange prop you will find the following:
onChange Callback fired when the value changes.
Signature:
function(event: object, value: T | T[], reason: string) => void
event: The event source of the callback.
value: The new value of the component.
reason: One of "create-option", "select-option", "remove-option", "blur" or "clear".
The third argument to onChange is the "reason" for the change. In your case, you want to ignore all of the "remove-option" changes:
onChange={(event, newValue, reason) => {
if (reason !== "remove-option") {
setValue(newValue);
}
}}
Here's a full working example:
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import Chip from "#material-ui/core/Chip";
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState([options[0]]);
const [inputValue, setInputValue] = React.useState("");
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
multiple
value={value}
disableClearable
onChange={(event, newValue, reason) => {
if (reason !== "remove-option") {
setValue(newValue);
}
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip key={option} label={option} color="primary" />
))
}
/>
</div>
);
}

Material UI autocomplete with redux-form values getting destroyed

I'm trying to use Material UI's autocomplete component with redux wizard form
I was able to link the autocomplete provided by material UI but when i go back to previous page and come back to the second page where the autocomplete fields are, the field gets destroyed despite having destroyOnUnmount set to false. (All other fields doesn't get destroyed but only the fields in page 2 which uses material UI autocomplete feature) Actually I dont think it's getting destroyed because the value is still there you just can't see it in the input
Also When I click Submit, the Main Hobby section's value gets through but the multiple hobby section's value doesnt get through. Can anyone have a look and see what's wrong? Thanks
You need to define the value attribute of AutoComplete to show the selected values when you visit the form again.
You must note that the two fields in Hobby form need to be defined with different name
Also the onChange value of multi select AutoComplete need to inform reduxForm about the change
MultipleComplete.js
import React from "react";
import { TextField } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
const hobbies = [
{ title: "WATCHING MOVIE" },
{ title: "SPORTS" },
{ title: "MUSIC" },
{ title: "DRAWING" }
];
const MultipleComplete = ({
input,
meta: { touched, error, submitFailed }
}) => {
const { onChange, ...rest } = input;
return (
<div>
<Autocomplete
multiple
limitTags={2}
value={input.value || []}
id="multiple-limit-tags"
options={hobbies}
onChange={(e, newValue) => {
onChange(newValue);
}}
getOptionLabel={option => option.title}
getOptionSelected={(option, value) => option.title === value.title}
renderInput={params => (
<TextField
{...params}
variant="outlined"
placeholder="Choose Multiple Hobbies"
fullWidth
/>
)}
/>
</div>
);
};
export default MultipleComplete;
AutoHobbyComplete.js
import React from "react";
import { TextField } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
const hobbies = [
{ title: "WATCHING MOVIE" },
{ title: "SPORTS" },
{ title: "MUSIC" },
{ title: "DRAWING" }
];
const AutoHobbyComplete = ({
input,
meta: { touched, error, submitFailed }
}) => {
const getSelectedOption = () => {
return hobbies.find(o => o.title === input.value);
};
const { onChange, ...rest } = input;
return (
<div>
<Autocomplete
autoSelect
value={getSelectedOption()}
options={hobbies}
autoHighlight
getOptionLabel={option => option.title}
onChange={(event, newValue) => onChange(newValue)}
getOptionSelected={(option, value) => {
return option.title === value.title || option.title === input.value;
}}
renderInput={params => {
return (
<TextField
{...params}
{...rest}
value={input.value}
variant="outlined"
fullWidth
/>
);
}}
/>
</div>
);
};
export default AutoHobbyComplete;
It seems that youre correct, the value just isnt being displayed properly. If you are able to get the value from your redux form you can pass that value as an inputValue to the autocomplete. This will display the value in the text box. Make sure to use inputValue and not value.
<Autocomplete
inputValue={//this is where your redux form value should be displayed}
autoSelect
options={hobbies}
autoHighlight
getOptionLabel={option => option.title}
onChange={(event, newValue) => console.log(newValue)}
getOptionSelected={(option, value) => option.title === value.title}
renderInput={params => (
<TextField {...params} {...input} value="test" variant="outlined" fullWidth />
)}
/>

Categories

Resources