MUI 5 Autocomplete choose attribute of object as value - reactjs

My options looks like this
const options = [
{
"VaccinationType": "Sample"
},
{
"VaccinationType": "Another Sample"
}
]
Code:
const [vacType, setVacType] = useState('');
<Autocomplete
value={vacType}
onChange={(e, value) => {
console.log(value);
setVacType(value);
}}
options={options}
getOptionLabel={(option) => option.VaccinationType}
isOptionEqualToValue={(option, value) => option.VaccinationType === value.VaccinationType}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
size="small"
/>
)}
/>
I tried logging my value it still outputs the object { VaccinationType: "Sample" }. I want it to only output "Sample"
In MUI 4 I'm using this getOptionSelected={(option, value) => option?.VaccinationType === value?.VaccinationType}

Autocomplete options care about the item of the array which you put. According to your code, each item has all object values.
I searched to find a way but I couldn't.
There is a way probably you already thought;
You can edit options parameter like options={options.map(o => o.VaccinationType)} and remove VaccinationType key on getOptionLabel and isOptionEqualToValue
<Autocomplete
value={vacType}
onChange={(e, value) => {
console.log(value);
setVacType(value);
}}
options={options.map(o => o.VaccinationType)}
getOptionLabel={(option) => option}
isOptionEqualToValue={(option, value) =>
option === value
}
renderInput={(params) => (
<TextField {...params} variant="outlined" size="small" />
)}
/>

So I think u forgot to use onInputChange and InputValue properties for MUI Autocomplete, according to https://mui.com/material-ui/react-autocomplete/ these properties are needed in this case here u can read more https://mui.com/material-ui/api/autocomplete/ so please check this solution which works for me:
const [vacType, setVacType] = useState<any>('Sample')
const [inputValue, setInputValue] = useState('Sample')
const options: any[] = [
{
VaccinationType: 'Sample'
},
{
VaccinationType: 'Another Sample'
}
]
<Autocomplete
value={vacType} //here u set default Value and change whenever it changes a list choice
inputValue={inputValue} //here u set default Input and change whenever it changes by typing
onChange={(event: any, newValue: any | null) => {
setVacType(newValue)
}}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue)
}} // handles typing
options={options}
getOptionLabel={(option: { VaccinationType: string }) => {
return option?.VaccinationType
}}
isOptionEqualToValue={(option, value) =>
option?.VaccinationType === value?.VaccinationType
}
renderInput={params => (
<TextField {...params} variant="outlined" size="small" />
)}
/>
Hope it helps!
Also to answer your problem with console.log
your code:
onChange={(e, value) => {
console.log(value);
setVacType(value);
}}
exchanged to :
onChange={(event: any, newValue: any | null) => {
console.log('newValue')
console.log(newValue)
setVacType(newValue)
}}
gives object but you on this Autocomplete provide objects then seting values is in objects so u just need to add console.log(newValue.VaccinationType) but not for setVacType(newValue) and Autocomplete will remember last choice

I don't know why you want to do that!,
You can simply use value object wherever you want, and use any key of the object, take the value object as it's then use value.VaccinationType.
But, if you want to do this:
<Autocomplete
{...props}
value={options.find(op => op.VaccinationType === vacType)}
onChange={(e, value) => {
console.log(value); //Result: { "VaccinationType": "Sample" }
setVacType(value.VaccinationType); // <==== Here, add `.VaccinationType`
}}
/>

Related

AUTOCOMPLETE MUI not accepting values entered in the textfield

i want the autocomplete to accept the value entered in the textfield even if it isn't present the dropdown.
my autocomplete component :
<Autocomplete
disabled={row.group_mapping_id && !('editClicked' in row)}
freeSolo
value={row.client_id}
onChange={(event, newValue) => props.onChangeTableInput('client_id', index, newValue, row)}
selectOnFocus
clearOnBlur
handleHomeEndKeys
options={props.clientIds}
renderOption={(props, option) => <li {...props}>{option.client_id}</li>}
autoHighlight
style={{width: '110px'}}
disableClearable
renderInput={(params) => <TextField {...params}
variant='outlined'
inputProps={{
...params.inputProps,
autoComplete: "disabled",
type: 'search'
}} />}
/>
clientIds array :
[{client_id : 'abcd'}, {client_id : 'abrd'}] and so on..
onChangeTableInput function :
const onChangeTableInput = (keyName, index, keyValue, rowData) => {
let temp_array = [...props.groupTableData];
let temp_value = keyValue
if(keyName === 'client_id'){
temp_array[index][keyName] = temp_value.client_id;
}else if(keyName === 'client_name'){
temp_array[index][keyName] = temp_value.client_name;
}else{
temp_array[index][keyName] = temp_value;
}
if (rowData && !rowData.group_mapping_id) {
temp_array[index]['operation'] = 'N';
}
props.setGroupTableData(temp_array);
}
i want the component to accept and display the typed option even if doesn't exist in the dropdown, right now if i don't type an existing option it doesn't display or read the value too.
<Autocomplete
{...otherProps}
defaultValue={{ client_id: row.client_id }}
getOptionLabel={option => option.client_id}
/>
And no need to renderOption={(props, option) => <li {...props}>{option.client_id}</li>}

MUI Autocomplete (multiple) controlled values - mysterious input behavior

I am trying to write code to asynchronously search a multiple-select combo upon keyboard entry.
However I found in latest version (5.2.2) a strange behaviour where I cannot explain. I distill the issue below (based on example from MUI's autocomplete page):
import * as React from "react";
import TextField from "#mui/material/TextField";
import Autocomplete from "#mui/material/Autocomplete";
const options = [
{ label: "Option 1", value: 1 },
{ label: "Option 2", value: 2 }
];
export default function ControllableStates() {
// const [value, setValue] = React.useState<any | null>([]);
const value = [];
const [inputValue, setInputValue] = React.useState("");
console.log("Current Value:", value);
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
multiple={true}
value={value}
onChange={(event: any, newValue: any | null) => {
//setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" />}
/>
</div>
);
}
The codeSandbox is as follows: https://codesandbox.io/s/controllablestates-material-demo-forked-ygqp2?file=/demo.tsx
If you try in the codeSandbox, you will be unable to type anything in the TextField field.
However, if you switch the commenting:
const [value, setValue] = React.useState<any | null>([]);
// const value = [];
You will be able to type in the TextField field. What is actually happening here? The value did not change at all.
Can anyone figure out why my first code (where the value is a const empty array) didn't work?
The reason I am asking is that I need to pass in the (controlled) value as props, and then set it to default to [] if it is null. I find that I am unable to type in the TextField due to this defaulting.
First, you could use the Autocomplete component without inputValue and OnInputValue props.
...
<Autocomplete
multiple
value={value}
onChange={(event: any, newValue: any | null) => {
//setValue(newValue);
}}
id="controllable-states-demo"
options={options}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" />}
/>
But it won't work for the selection, only search will work.
Second, if you want its search as well as selection to work, then you should use more a couple of Autocomplete props.
...
export default function ControllableStates() {
const [value, setValue] = React.useState<any | null>([]);
// you need to set the selected value your own
// const value = [];
const [inputValue, setInputValue] = React.useState("");
console.log("Current Value:", value);
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
multiple
value={value}
onChange={(event: any, newValue: any | null) => {
setValue(newValue.map(option => option.value || option));
}}
isOptionEqualToValue={(option, value) => option.value === value}
getOptionLabel={(option) => {
if (typeof option === 'number') {
return options.find(item => item.value === option)?.label;
} else {
return option.label;
}
}}
id="controllable-states-demo"
options={options}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" />}
/>
</div>
);
}
As you can see it doesn't need to use the inputValue and onInputChange props as well.
Please make sure if you match the correct types of the selected value and option.
If you are using react-hook-form you can set up the autocomplete by using
multiple to add multiple values,
options: you add the options to be selected
getOptionLabel: to show up the label of the options
onChange: use onChange function of react-hook-form to set the selected values
renderInput: to render the input
import { useForm, Controller } from 'react-hook-form'
import {
Box,
TextField,
Autocomplete,
} from '#mui/material'
const {
...
control,
formState: { errors },
} = useForm()
<Box mt={2}>
<Controller
control={control}
name="industries"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange } }) => (
<Autocomplete
defaultValue={
useCasesData?.industries
? JSON.parse(useCasesData?.industries)
: []
}
multiple
disableCloseOnSelect
options={companyIndustryTypes}
getOptionLabel={(option) => option.name}
onChange={(event, values) => {
onChange(values)
}}
renderInput={(params) => (
<TextField
{...params}
label="Type d'industries"
placeholder="Type d'industries"
helperText={errors.industries?.message}
error={!!errors.industries}
/>
)}
/>
)}
/>
</Box>
Note that options in my case companyIndustryTypes is an array of object :
[
{
id: 1,
name: "Accounting",
},
{
id: 2,
name: "Administration & Office Support",
},
...
]

Showing a value (instead of label) on a Material UI Autocomplete

So I have an async combo box that pulls options from an API. The options are just an Id and a description. This component is part of a form that I'm showing to add or edit data. What I'd like to see happen is have the option be empty when adding new data and to have the current value selected when editing. Instead, it simply shows the label.
This is my code that's almost a copypaste of the example from the docs.
export default function AsyncAutoComplete(props:AsyncAutoCompleteProps) {
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState<EntityWithIdAndDescription[]>([]);
const loading = open && options.length === 0;
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
props.populateWith().then((options)=> {
if (active) {
setOptions(options);
}})
})();
return () => {
active = false;
};
}, [loading]);
React.useEffect(() => {
if (!open) {
setOptions([]);
}
}, [open]);
return (
<Autocomplete
id="async-autocomplete"
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
onChange={props.onChange}
getOptionSelected={(option, value) => option.id === value.id}
getOptionLabel={(option) => option.description}
options={options}
loading={loading}
renderInput={(params) => (
<TextField
{...params}
label={props.label}
variant="outlined"
margin="dense"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
What I want is to pass an Id value to this component and have it show the description for that value as the selected option (after making the API call). Using defaultValue doesn't seem to work.
Any advice with either modifying this or taking a different approach would be helpful.
Looks like what you're after is a controlled component. There's an example of this in Material UI's Autocomplete demo under Playground called controlled.
I'm not sure how you're getting the id of the initial value you want to pass to the component to show that it's selected. It could be something like the following.
Create a separate state for the value you select from this Autocomplete in your parent component. In fact, I would not have a separate component called AsyncAutocomplete at all. This is so you control all your state in the parent component and the Autocomplete component becomes purely presentational.
After your API call is complete and the setOptions(options) is called, call setValue with the value that you would like to show selected. This must be of type EntityWithIdAndDescription.
Create an inline-function for the onChange prop of the Autocomplete component which takes a the second parameter as the EntityWithIdAndDescription | null type. This is what's required from Autocomplete's onChange. Call setValue with this parameter as the argument.
Pass options, value, onChange and loading as props into the Autocomplete component. The additional props I've passed over and above what you've done in your code are:
<Autocomplete
...
disabled={loading}
value={value}
...
/>
Let me know how you go
const [value, setValue] = useState<EntityWithIdAndDescription | null>(null); // (1)
const [options, setOptions] = useState<EntityWithIdAndDescription[]>([]);
const loading = options.length === 0;
useEffect(() => {
populateWith().then((options)=> {
setOptions(options);
})
// (2)
setValue({
id: "something",
description: "something",
})
return () => {};
}, []);
// (4)
<Autocomplete
id="async-autocomplete"
disabled={loading}
onChange={(event: any, newValue: EntityWithIdAndDescription | null) => {
setValue(newValue); // (3)
}}
getOptionSelected={(option, value) => option.id === value.id}
getOptionLabel={(option) => option.description}
options={options}
loading={loading}
value={value}
renderInput={(params) => (
<TextField
{...params}
label={"My Entities"}
variant="outlined"
margin="dense"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
Code Sandbox
Here's an example with the Autocomplete in a separate component I called MyAutocomplete. It includes an API call and setting a value I want to be selected first.
https://codesandbox.io/s/autumn-silence-lkjrf

How Can i limit the maximum number of options that can be selected in a Material UI lab Autocomplete component

(for instance) I wish to limit user selecting only 3 options in my Autocomplete component, and disable the options when the length of TAG Array reaches 3.
The problem is there is no limitMaxNumberOfTags option in the api, and i cant get any way to access the Selected tags array whatsoever {except the limitTags, which only limits the visible tags}
.
something along the lines of this might help
getOptionDisabled={(options, tags) => (tags.length > 3 ? true : false)}
.
Here is my autocomplete implementation so far
<Autocomplete
multiple
id="tags-outlined"
options={students}
getOptionLabel={(option) => option.personalInfo.firstName + ' ' + option.personalInfo.lastName}
defaultValue={[...added]}
onChange={(e, newVal) => setAdded([...newVal])}
renderOption={(option, state) => {
return (
<Chip
icon={
<FaceIcon /> /*<Avatar color="primary" variant='outlined' size="small" className={classes.small}></Avatar>*/
}
label={option.personalInfo.firstName + ' ' + option.personalInfo.lastName}
color="default"
variant="outlined"
{...state}
/>
);
}}
renderTags={(options, getTagProps) =>
options.map((option) => (
<Chip
icon={
<FaceIcon /> /*<Avatar color="primary" variant='outlined' size="small" className={classes.small}></Avatar>*/
}
label={option.personalInfo.firstName + ' ' + option.personalInfo.lastName}
color="primary"
variant="outlined"
{...getTagProps({})}
/>
))
}
filterSelectedOptions
filterOptions={(options, state) =>
options.filter((option) => {
for (let i = 0; i < added.length; i++) {
if (added[i]._id === option._id) {
return false;
}
}
return true;
})
}
// ---> getOptionDisabled={(options) => (tags.length > 3 ? true : false)}
renderInput={(params) => (
<TextField {...params} variant="outlined" color="primary" label="Select Students" placeholder="Participant" />
)}
/>
Ran into similar issue recently. This is what I ended up doing. Basically you have to set the disabled flag on the chip itself directly, so it disables the text input, but not the chip. So you can still delete each chip.
export const AutoCompleteWithLimit: React.FC<Props> = ({
disabled = false,
limit = 2,
}) => {
const [disableInput, setDisableInput] = useState<boolean>(
value.length >= limit
);
return (
<Autocomplete
// Set disabled based on input
disabled={disabled || disableInput}
multiple
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip
key={index}
label={option.name}
{...getTagProps({ index })}
// Set disable explicitly after getTagProps
disabled={disabled}
/>
))
}
onChange={(_event: any, newValue: any[]) => {
// do something else
// set the disable input
setDisableInput(newValue.length >= limit);
}}
/>
);
};
Autocomplete added getOptionDisabled prop that can be used to disable all non selected options after maximum number of options is selected.
const a = ({limit = 3})=> {
const [limitReached, setLimitReached] = useState(false);
const [values, setValues] = useState([]);
const onSelect = useCallback((newValues) => {
setValues(newValues);
setLimitReached(newValues.length >= limit);
}, [limit]);
const checkDisable = useCallback(option => limitReached && !values.includes(option), [limitReached, values]);
return <Autocomplete
getOptionDisabled={checkDisable}
multiple
onChange={onSelect}
options={options}
value={values}
/>
}
In my case, this worked fine.
onChange={(e, newVal) => {
if (newVal > 3) {
newVal.pop();
}
setAdded([...newVal]);
}
Though the documentation explains that the value parameter of onChange prop is the new value, the actual content seems the array of all selected options.
And the last element in the value array is the newly selected option by the current change event. Therefore the code snippet above eliminates the exceeding element against the limited number (3 as above).
The latter type (Array<T>) for value parameter in the documentation is the case.
I kind of found one solution to deal with it on my own, after fiddling through documentation for some unrelated Problem.
The solution includes
Setting value = {[...myStateVariable]}
Using the reason parameter from the onChange((event, newValue, reason)=>{....}) callback
<Autocomplete
multiple
id="tags-outlined"
options={students}
getOptionLabel={(option) => option.personalInfo.firstName+' '+option.personalInfo.lastName}
defaultValue={[...added]}
value={[...added]}
onChange={(e, newVal, reason)=>{
if(reason==="select-option"){
addChips(newVal)
} else if(reason==="remove-option"){
handleDelete(newVal)
}
}}
The HandleDelete and addChips method are as follows.
const [added, setAdded] = useState([...added2])
const handleDelete = (students)=>{
setAdded([...students])
}
const addChips = (students)=>{
if(added.length>= maxParticipation){
alert('Cannot add more participants')
}else{
setAdded([...students])
}
}
the 'newValue' is first intercepted by the 'onChange' callback where it's length is evaluated and if the length is greater than a limiting value, the value update is cancelled. (Notice, the onChange that causes abortion of the process is only the one where "reason" = 'select-option' ).
P.S- (forgot to mention about the disable Options query) the "disable" attribute on Options could also be manipulated in the renderOptions to disable all the options after "newVal" reaches a given length.
[ solution for that(disable options) given in detail by #justindra ]
I used slice to limit the maximum limit on renderTags before rendering the chips
<Autocomplete
renderTags={(options, getTagProps) =>
options.slice(0, 5).map((option) => (
<Chip
icon={
<FaceIcon /> /*<Avatar color="primary" variant='outlined' size="small" className={classes.small}></Avatar>*/
}
label={option.personalInfo.firstName + ' ' + option.personalInfo.lastName}
color="primary"
variant="outlined"
{...getTagProps({})}
/>
))
}
/>
<Autocomplete
multiple
id="role"
options={rootStore.roleStore.listRole}
disableCloseOnSelect
value={rootStore.userStore.user.role}
onChange={(event, newValue) => {
setErrors({
...errors,
role: Utils.validate.single(
newValue,
Utils.constraintsUser.role
),
})
if (newValue.length > 2) {
alert("Please select max 2 labs")
} else {
rootStore.userStore.updateUser({
...rootStore.userStore.user,
role: newValue,
})
}
}}
getOptionLabel={(option) => option.description || ""}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox style={{ marginRight: 8 }} checked={selected} />
{option.description}
</React.Fragment>
)}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="Labs"
placeholder="Labs"
/>
)}
/>
perfect solution..
My solution,I think it's the most properly way:
<Autocomplete
value={selectedTags}
onChange={(ev, value: Tag[]) => {
if (value.length <= 3) {
setSelectedTags(value);
} else {
return;
}
}}
/>;

How can I clear autocomplete values

I am working with material ui autocomplete.
<Autocomplete
multiple
id="tags-outlined"
options={options}
getOptionLabel={(option) => option && option.name}
value={catArray}
onChange={handleCategoryFilter}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
placeholder="Categories"
/>
)}
/>
Firstly I want selected value not to show in options secondly I want to clear autocomplete. Can you please guide me how can I do these things.
I am clearing the values by setCatArray([]) but it is not workinh
You can use material ui autocomplete's default prop filterSelectedOptions. If true, it will hide the selected options from the list box.
For clearing the values of autocomplete, it gives default clear icon at the end of the select box. you can clear it from there.
Try my sandbox link. Also try some more material ui demos here.
UPDATE: If you want to manage clearing values you can use value prop of autocomplete and manage it in onchange by updating it's value.
<Autocomplete
multiple
id="tags-outlined"
options={top100Films}
value={values}
getOptionSelected={(option, value) => value.title === option.title}
getOptionLabel={(option) => option.title}
filterSelectedOptions
onChange={(e, valueTags) => {
e.preventDefault();
const valuesArray = [];
valueTags.forEach((valueTag) => {
valuesArray.push({
title: top100Films
.filter((tag) => valueTag.title === tag.title)
.shift().title
});
});
setValues(valuesArray);
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
Have you tried adding autocomplete=false to the list of properties in the component?
If you want to filter out selected options from autocomplete. Pass filterSelectedOptions={true} to your component
Secondly, you can clear the value by taking control of the selected values and setting it to an empty array. Here is my complete example
const options = ["one", "two", "three", "four"];
export default function MyAutocomplete() {
const [values, setValues] = React.useState<string[]>([]);
const onChange = (_, value) => {
setValues(value);
};
const clearSelected = () => {
setValues([]);
};
return (
<>
<button onClick={clearSelected}>Clear selected</button>
<Autocomplete
multiple
id="tags-outlined"
options={options}
getOptionLabel={(option) => option}
value={values}
onChange={onChange}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} variant="outlined" placeholder="Categories" />
)}
/>
</>
);
}
Live Example
const options = ["one", "two", "three", "four"];
export default function MyAutocomplete() {
const [values, setValues] = React.useState([]);
const onChange = (_, value) => {
setValues(value);
};
const clearSelected = () => {
setValues([]);
};
return (
<>
<button onClick={clearSelected}>Clear selected</button>
<Autocomplete
multiple
id="tags-outlined"
options={options}
getOptionLabel={(option) => option}
value={values}
onChange={onChange}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} variant="outlined" placeholder="Categories" />
)}
/>
</>
);
}

Resources