How to control Material-ui <Autocompelete /> by option id - javascript

When working with HTML select in React, we tend to use an id or key to track the value selected:
<select value={value} onChange={(event) => setValue(event.target.value)}>
{options.map((option) => (
<option value={option.id}>{option.label}</option>
))}
</select>
I wonder if we can do the same with Material-ui Autocompelete component since in its demo, the value set in state is the whole object instead of the object id.
I tried using its APIs in the following way which make sense to me but it doesn't work as expected:
const fruits = [
{ id: 0, label: "apple" },
{ id: 1, label: "banana" },
{ id: 2, label: "cherries" },
{ id: 3, label: "fig" }
];
function FruitPicker() {
const [value, setValue] = useState(null);
return (
<Autocomplete
id="fruit-picker"
value={value}
onChange={(event, option) => {
setValue(option?.id || null);
}}
options={fruits}
getOptionLabel={(option) => option.label}
getOptionSelected={(option) => option.id === value}
renderInput={(params) => <TextField {...params} label="Fruit" />}
openOnFocus
/>
);
}
I had created this Codesandbox if you want to play around. Thanks.

This is the method that I used.
<Autocomplete
options={fruits}
value={fruits.filter(el => el.id === currentValue)[0]}
getOptionLabel={option => option.label}
onChange={(event, option) => { setValue(option?.id || null); }}
/>

Because you pass the options of array object, so when set value onChange, you must still keep setValue(option), but on getOptionSelected, compare their ids instead
<Autocomplete
value={value}
onChange={(event, option) => {
setValue(option);
}}
options={fruits}
getOptionLabel={(option) => option.label}
getOptionSelected={(option) => option.id === value.id}
renderInput={(params) => <TextField {...params} label="Fruit" />}
openOnFocus
/>

Related

React - MUI Autocomplete single selection remove event

I use material UI autocomplete to create a single-select dropdown.
But the problem is that when I click the close button placed right-side of the input, the onChange event didn't call and my state didn't update.
While in the multi-select mode this event successfully occurred.
Someone helps me to use the remove event in single-select mode.
This is my single select:
<Autocomplete<Option<T>>
onChange={(e: any, newValue) => {
if (newValue) {
handleChangeValue(newValue.value);
}
}}
sx={{ ...sx }}
id={id}
options={options}
isOptionEqualToValue={(newValue, option) =>
newValue.value === option.value
}
defaultValue={
defaultValue && {
value: defaultValue,
label: options.find((option) => option.value === defaultValue)?.label,
}
}
value={
value && {
value: value,
label: options.find((option) => option.value === value)?.label,
}
}
getOptionLabel={(option) => option.label || `${option.value}`}
renderOption={(props, option, { selected }) => (
<li value={option.value} {...props}>
<Checkbox
style={{ marginRight: 8 }}
checked={selected}
id={`${option.value}`}
/>
{option.label || `${option.value}`}
</li>
)}
renderInput={(params) => (
<TextField
value={value}
{...params}
placeholder={global.translate(placeholder)}
/>
)}
/>
this one is my multi-select autocomplete:
<Autocomplete
onChange={(e: any, value) => {
onChange(value);
}}
value={value}
sx={{ ...sx }}
multiple
id={id}
options={options}
disableCloseOnSelect
defaultValue={[...defaultValues]}
getOptionLabel={(option) => option.label}
isOptionEqualToValue={(newValue, option) =>
newValue.value === option.value
}
renderOption={(props, option, { selected }) => (
<li value={option.value} {...props}>
<Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
checkedIcon={<CheckBoxIcon fontSize="small" />}
style={{ marginRight: 8 }}
checked={selected}
id={option.value}
/>
{option.label}
</li>
)}
renderInput={(params) => (
<TextField {...params} placeholder={placeholder} />
)}
/>
In your single-select mode Autocomplete component, you send handleChangeValue if only newValue exists.
So, in your single-select mode, you need to change this code:
<Autocomplete<Option<T>>
onChange={(e: any, newValue) => {
if (newValue) {
handleChangeValue(newValue.value);
}
}}
to this code:
<Autocomplete<Option<T>>
onChange={(e: any, newValue) => {
const valueToBeSent = newValue ? newValue.value : undefined;
handleChangeValue(valueToBeSent);
}}
in order to send onChange event on every value change.

Multiple Select with options as an object array

I have the following component for selecting roles:
export const MultipleSelectChip = ({
options,
label,
error,
onRolesUpdate,
}: Props) => {
const theme = useTheme();
const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
const handleChipChange = (
event: SelectChangeEvent<typeof selectedOptions>,
) => {
const {
target: { value },
} = event;
setSelectedOptions(
// On autofill we get a the stringified value.
typeof value === 'string' ? value.split(',') : value,
);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="multiple-chip-label">{label}</InputLabel>
<Select
required
labelId="multiple-chip-label"
error={error}
id="demo-multiple-chip"
multiple
value={selectedOptions}
onChange={handleChipChange}
input={<OutlinedInput id="select-multiple-chip" label={label} />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{options.map((propOption) => (
<MenuItem
key={propOption.id}
value={propOption.name}
style={getStyles(propOption, selectedOptions, theme)}
>
{propOption.name}
</MenuItem>
))}
</Select>
<FormHelperText>Here's my helper text</FormHelperText>
</FormControl>
</div>
);
};
For options I have an array of objects with id and name, the thing is that I want to use the names for displaying the chips and the ids to pass them to the parent component for the add request. I don't know how to get de ids, too.
This is the example: https://codesandbox.io/s/6ry5y?file=/demo.tsx but is using an array of strings instead of an array of objects.
This is how 'options' looks like:
const rolesDummy: Role[] = [
{ id: '61fb0f25-34aa-46c6-8683-093254223dcd', name: 'HR' },
{ id: '949b9b1e-d3f8-45cb-a061-08da483bd486', name: 'Interviewer' },
{ id: 'c09ae2d4-1335-4ef0-8d4b-ee9529796b52', name: 'Hiring Manager' },
];
And I need to get back only the selected ids
Thank you!
If you pass the option as an object, you can render each MenuItem with the option.id as a key and the option.name as the label. The MenuItem is identified by an id:
<Select {...}>
{options.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
To display the name in the selected Chip. Use renderValue, but it only provides you the selected values (array of option.id), so you need to find the option to get the name:
renderValue={(selected) => {
return (
<Box>
{selected.map((value) => {
const option = options.find((o) => o.id === value);
return <Chip key={value} label={option.name} />;
})}
</Box>
);
}}
Now you can get an array of selected ids by adding a change handler:
onChange={e => console.log(e.target.value)}

Warning: Encountered two children with the same key, 'Device 846' in Autocomplete of material-ui [duplicate]

I'm trying to create a Material-UI Autocomplete component that essentially just displays search results to the user. Some of the options' names will be duplicates, but they will all have unique IDs. I receive the following warning:
index.js:1 Warning: Encountered two children with the same key, Name B. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
const SearchField = () => {
const [open, setOpen] = React.useState(false)
const [searchQuery, setSearchQuery] = React.useState('')
const [searchResults, setSearchResults] = React.useState([])
const loading = true //later
const debounced = useDebouncedCallback(
async searchQuery => {
if (searchQuery) {
let result = await doSearch(searchQuery)
if (result.status === 200) {
setSearchResults(result.data)
} else {
console.error(result)
}
}
},
1000
)
const handleInputChange = e => {
if (e.target.value && e.target.value !== searchQuery) {
debounced(e.target.value)
setSearchQuery(e.target.value)
}
}
const options = [{
name: 'Name A',
id: 'entry_0597856'
},{
name: 'Name B',
id: 'entry_3049854'
},{
name: 'Name B',
id: 'entry_3794654'
},{
name: 'Name C',
id: 'entry_9087345'
}]
return (
<Autocomplete
id='search_freesolo'
freeSolo
selectOnFocus
clearOnBlur
handleHomeEndKeys
autoHighlight
onInputChange={handleInputChange}
open={true}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
loading={loading}
key={option => option.id}
options={options}
getOptionLabel={option => option.name}
renderOption={(props, option) => (
<Box
component='li'
{...props}
>
{option.name}
</Box>
)}
renderInput={params => {
return (
<TextField
{...params}
required
id="search_bar"
label="Search"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress size={18} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
)}
}
/>
)
}
You can define your own renderOption that can return the list item with a correct key value. Your code complains about the duplicated keys because by default, Autocomplete uses the getOptionLabel(option) to retrieve the key:
<Autocomplete
renderOption={(props, option) => {
return (
<li {...props} key={option.id}>
{option.name}
</li>
);
}}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
If it still doesn't work, check your props order, you need to declare the key prop last, if you put it before the props provided by the callback:
<Box component='li' key={key} {...props}
Then it will be overridden by the props.key from MUI. It should be like this:
<Box component='li' {...props} key={key}
Live Demo

React Material UI Autocomplete - how to display text name as select but send ID as a value?

MATERUAL UI Autocomplete component works fine, but I want to get object.id as onSelect event value (event.target.value), not the object.name. In other words, I want to display object.name as select item labels, but I want to get object.id as onSelect event value (event.target.value). Right now, my event.target.value is the same as select item label (object.name). Here is an example (from the Material UI documentation):
The options object is like this:
const options = [
{ id: "01", name: "Peter" },
{ id: "02", name: "Mary },
{ id: "03", name: "John" }
]
And the Autocomplete is the same like in Material UI documentation:
<Autocomplete
id="asynchronous-demo"
fullWidth
open={open}
onOpen={() => {
setOpen(true)
}}
onClose={() => {
setOpen(false)
}}
getOptionLabel={(option) => option.name}
options={options}
loading={loading}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
onChange={(event) => {
if (event.target.value !== '' || event.target.value !== null) {
onChangeHandle(event.target.value)
}
}}
onSelect={(event) => {
onSelectHandle(event)
}}
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
With onSelect I always get object.name as event.target.value, but I want to return object.id as event.target.value.
Does anybody knows how??
You are currently grabbing the value from TextField's onSelect, instead of Autocomplete's onChange.
<Autocomplete
...
onChange={(event, newValue) => {
onSelectHandle(newValue)
}}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
onChange={(event) => {
if (event.target.value !== '' || event.target.value !== null) {
onChangeHandle(event.target.value)
}
}}
...
/>
)}
/>
For more info, check out the controllable states section in the Autocomplete documentation.

Getting the value in the React material-UI Autocomplete

I am referring to the documentation of React Material-UI (https://material-ui.com/components/autocomplete/).
In the demo code,
<Autocomplete
options={top100Films}
getOptionLabel={(option: FilmOptionType) => option.title}
style={{ width: 300 }}
renderInput={params => (
<TextField {...params} label="Combo box" variant="outlined" fullWidth />
)}
/>
I get how it works, but I am not sure how I am supposed to get the selected value.
For example, I want to use the onChange prop to this so that I can make some actions based on the selection.
I tried adding onChange={v => console.log(v)}
but the v does not show anything related to the selected value.
Solved by using passing in the (event, value) to the onChange props.
<Autocomplete
onChange={(event, value) => console.log(value)} // prints the selected value
renderInput={params => (
<TextField {...params} label="Label" variant="outlined" fullWidth />
)}
/>
The onChange prop works for multiple autocomplete values as well (#Steve Angello #ShwetaJ). The value returned is a list of all the selected options.
const [selectedOptions, setSelectedOptions] = useState([]);
const handleChange = (event, value) => setSelectedOptions(value);
const handleSubmit = () => console.log(selectedOptions);
.
.
.
<Autocomplete
multiple
autoHighlight
id="tags-outlined"
options={top100Films}
getOptionLabel={(option) => option.title}
onChange={handleChange}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="standard"
/>
)}
/>
.
.
.
<button onClick={handleSubmit}>Submit!</button>
You can use useState to store the recevied value and onChange to get the value:
const [selected, setSelected] = useState([]);
return (
<Autocomplete
onChange={(event, value) => setSelected(value)}
renderInput={(params) => <TextField {...params} label="selected" />}
/>
);
Here is TS working example
const tags = ["perimeter", "Algebra", "equation", "square root"];
const handleInput = (e: React.SyntheticEvent, value: string[]) => {
console.log(value);
};
<Autocomplete
multiple
options={tags}
onChange={handleInput}
renderTags={(value: readonly string[], getTagProps) =>
value.map((option: string, index: number) => (
<Chip
variant="outlined"
label={option}
{...getTagProps({ index })}
/>
))
}
renderInput={(params) => (
<TextField
{...params}
variant="filled"
label="Tags"
placeholder="select question tags"
/>
)}
/>
============ Output ===============

Categories