I have 2 select boxes that i am building uisng the autocomplete component in material ui - https://mui.com/material-ui/react-autocomplete/#checkboxes
I have a edge case im trying to fix:
A user selects both "Consumer Electronics" and "Fashion & Apparel" in the first main industries select box.
A user then selects in the second sub industries select box, "Computer & Laptops" and "Eyewear". (1 from each group).
If a user then removes "Fashion & Apparel" from the first select box, all of the items that were selected with that category should be removed in the second select box.
example:
https://codesandbox.io/s/checkboxestags-demo-material-ui-forked-qox19g?file=/demo.tsx
You can see in the second "Autocomplete" im using the "filterOptions" to only show options depending on the first "Autocomplete"
also see data.json in the example for the raw data.
code:
return (
<Autocomplete
multiple
options={subIndustries}
filterOptions={(x) =>
x.filter((x) =>
getValues("main_industries").find((m) => m.name === x.category)
)
}
groupBy={(option) => option.category}
disableCloseOnSelect
getOptionLabel={(option) => option.name}
onChange={(_, newValue: any) => {
setValue("sub_industries", newValue, { shouldValidate: true });
}}
defaultValue={getValues("sub_industries")}
onInputChange={(_, val) => {
console.log(val);
}}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={uncheckedIcon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.name}
</li>
)}
renderInput={(params) => (
<TextField
{...params}
label="Sub Industries"
placeholder="Sub Industries"
/>
)}
/>
);
};
Before all, some points related to your code:
On Autcocomplete of SubIndustrySelector function, you should use value instead defaultValue. defaultValue should be use when component is not controlled.
You also need to include the isOptionEqualToValue prop to both Autocomplete . Your code claims to use it. According to Mui docs, isOptionEqualToValue is necessary for:
Used to determine if the option represents the given value. Uses strict equality by default. ⚠️ Both arguments need to be handled, an option can only match with one value.
So you just need to guarantee that the option is equal the value with:
isOptionEqualToValue={(option, value) => option.id === value.id}
Related to the issue you are facing, the SubIndustrySelector isn't get update because you are controlling what tags should render using getValues instead watch from react-hook-form.
According to react-hook-form docs, getValues are:
An optimized helper for reading form values. The difference between watch and getValues is that getValues will not trigger re-renders or subscribe to input changes
(You can check more about it here and here.)
So, your SubIndustrySelector function should be something like this:
// WATCH ON sub_industries
const watchSubIndustries = watch("sub_industries");
const SubIndustrySelector = () => {
if (!dataJson) {
return <Skeleton height={"100%"} variant="text" />;
}
const subIndustries = dataJson
.map((i) => {
return i.industries;
})
.flat()
.filter(Boolean);
return (
<Autocomplete
multiple
options={subIndustries}
filterOptions={(x) =>
x.filter((x) =>
getValues("main_industries").find((m) => m.name === x.category)
)
}
groupBy={(option) => option.category}
disableCloseOnSelect
getOptionLabel={(option) => option.name}
// isOptionEqualToValue CONTROL
isOptionEqualToValue={(option, value) => option.id === value.id}
onChange={(_, newValue: any) => {
setValue("sub_industries", newValue, { shouldValidate: true });
}}
//defaultValue={watchSubIndustries}
value={watchSubIndustries}
renderOption={(props, option, { selected }) => {
return (
<li {...props}>
<Checkbox
icon={uncheckedIcon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.name}
</li>
);
}}
renderInput={(params) => {
return (
<TextField
{...params}
label="Sub Industries"
placeholder="Sub Industries"
/>
);
}}
/>
);
};
You can check the code above working in this code sample.
As you can see, the changes that i did are:
before the SubIndustrySelector function i added a watch to sub_industries -> watchSubIndustries.
Added isOptionEqualToValue prop
Changed defaultValue to value prop and add watchSubIndustries as value.
If you have any questions just ask in the comments.
Codesandbox with minimal working example: https://codesandbox.io/s/mystifying-lake-m0oxc?file=/src/DemoForm.tsx
Essentially, I have a AutoComplete dropdown which is tied into a Formik form with the useField hook. This sets the value correctly on any change, but the validation doesn't seem to trigger when I expect it to.
Validation runs successfully and removes the error if:
Another field is changed
I click on the "background" after selecting a value in the dropdown
What I expected and wanted was that the validation should run immediately when a value is selected.
To reproduce:
Click the category dropdown
Select a value, which closes the dropdown
Confirm that the error is still indicated (on the field and in the printout)
Click elsewhere on the screen. This should trigger validation and clear the error.
Any suggestions?
Edit:
I've tested an equivalent implementation with react-select as the dropdown and had the same issue, so I don't think it's directly tied to MUI.
I just reproduced based on the working example what you provided and realized that you set helpers.setTouched manually.
Just don't overuse the setTouched and also you need to handle if you remove the selected item from the autocomplete.
<Autocomplete
disablePortal
id="category-selector"
options={options}
onBlur={() => helpers.setTouched(true, true)}
isOptionEqualToValue={(option, value) => option.id === value.id}
onOpen={() => helpers.setTouched(true, true)}
onChange={(_, option) => {
if (option) {
helpers.setValue(option.id);
} else {
helpers.setValue(0);
}
}}
renderInput={(params) => (
<TextField
{...params}
label="Category"
error={meta.touched && Boolean(meta.error)}
/>
)}
/>
i got a error when i click on the clear cross button of a autocomplete with material ui.
Here my AutoComplete:
<Autocomplete
autoComplete
fullWidth
options={esportTeams.list}
getOptionLabel={(option) => option.name ?? ""}
className={classes.formControl}
onChange={(e, value) => setFilter({ ...filter, team: value.name })}
renderInput={(params) => <TextField {...params} fullWidth label="Teams"
variant="outlined" value={filter.team ?? ""} size="small" />}
/>
When i click on the cross for clear the field i got this error : Cannot read property 'name' of null and i have test a lot of things but no one works ...
Thanks !
EXPLANATION:
If you deeply think about the error, when you press the X button inside the Autocomplete component it triggers the onChange event because it changes the value to null
onChange={(e, value) => setFilter({ ...filter, team: value.name })}
Objects are complex data structures. The simplest object in JavaScript is the plain object — a collection of keys and associated values:
let myObject = {
companyName: 'Exploretale Technologies, OPC'
};
There are situations when an object cannot be created. For such cases, JavaScript provides a special value null — which indicates a missing object.
let myObject = null;
"null is a primitive value that represents the intentional absence of any object value."
That's why you're having this "TypeError: Cannot read properties of null (reading 'name')"
SOLUTION:
onChange={(e, value) =>
if(value === null) {
//Just give a value to a value to avoid null
//e.g. value = ""
}
setFilter({ ...filter, team: value.name })}
Your esportTeams.list should be an array of strings or numbers. It doesn't accept objects, your value is selected item in the input. So you can't look for a property on that.
I think you intended to do this;
onChange={(e, value) => setFilter({ ...filter, team: value })}
Thanks for reading. I'm using Material-UI Autocomplete and trying to figure out how to capture a user choice. Here's the code. For now, I'm trying to console.log the user choice or deletion of a movie from the selection list. If I can get this to work I'll probably use a state settor in handleChange to capture the movie and read the state value for processing once the dialog is submitted. Since this is a multiple Autocomplete there may be several selected values.
Currently event.target.value in handleChange displays a zero when selecting a movie from the list, and undefined when removing. I need it to display the actual title.
Happy to consider any suggestion to get to the objective of being able to process all autocomplete selections upon dialog submission. Thanks in advance!
React version: 16.8
Resolved by sending the parameters (event, value) from onChange. Please see the onChange value in the below snippet.
export default function App() {
const classes = useStyles();
const handleChange = (value) =>
{
const x = value.map(function(a){return {title:a.title};});
console.log(x);
};
return (
<div className={classes.root}>
<Autocomplete
multiple
id="tags-outlined"
options={top100Films}
onChange={(event, value) => handleChange(value)}
getOptionLabel={(option) => option.title}
defaultValue={[top100Films[14]]}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
</div>
);
}
I have one problem in my react code.
I use Material-ui and redux-form. I have select input like and after change this select i should reset value in . I use action 'change' from react-form and set value for textfield. But label in still remains. Can i clear or reset value in ?
<Autocomplete
options={list}
getOptionLabel={option => option.name}
onInputChange={onChange}
onChange={onChangeAutoComplete}
noOptionsText='Нет доступных вариантов'
loadingText='Загрузка...'
openText='Открыть'
renderInput={params => (
<Field
{...params}
label={label}
name={fieldName}
variant="outlined"
fullWidth
component={renderTextField}
className={classes.textField}
margin="normal"
/>
)}
/>
Using hooks on the value prop breaks the functionality of the autocomplete component ( at least for me ). Using class, and setting the local state is the same.
Luckily it is a react component, so it have a "key" prop. When the key prop changes, the component is re-rendered with the default values ( which is an empty array since nothing is selected). I used hooks in the parent component and passed the values to the key prop, whenever reset is needed.
<Autocomplete
key={somethingMeaningful} // Bool, or whatever just change it to re-render the component
//...other props
/>
Hope this helps!
Material UI Autocomplete onInputChange callback provides reason argument. If input has been changed by input, reason will be input and if you selected option then reason will be reset.
onInputChange={(event, newInputValue, reason) => {
if (reason === 'reset') {
setValue('')
return
} else {
setValue(newInputValue)
}
}}
setValue is useState and you can pass value state to autocomplete value property.
use value in your <Autocomplete /> like this:
<Autocomplete
value={this.state.value} //insert your state key here
//...other props
/>
Then clear state of that key, to clear the autocomplete field value
I am going to post a very dirty way of clearing the value of Autocomplete. Try it ONLY when nothing else works;
import React, { useRef } from 'react';
...
const autoC = useRef(null);
...
<Autocomplete
...
ref={autoC}
/>
and then when you want to clear the value;
const ele = autoC.current.getElementsByClassName('MuiAutocomplete-clearIndicator')[0];
if (ele) ele.click();
This is what worked for me.
const [name, setName] = useState('');
<Autocomplete
inputValue={name}
onChange={(e,v)=>setName(v?.name||v)}
...
/>
<Button onClick={()=>setName('')}>
Clear
</Button>
You can use something like the following to clear the autocomplete field when an item is selected.
<Autocomplete
value={null}
blurOnSelect={true} />
Note that you may also need to set clearOnBlur={true} if you're using the freeSolo option.
Source https://mui.com/api/autocomplete/#props
I achieved this by updating the inputValue prop where multiple prop is false. If you are using multiple prop, then there is a propblem (bug). Selected values does not get erased.
When I encountered this, it was when options for the autocomplete changed, and wanted to clear the input value. It wouldn't clear with just the options changing. What worked for me is adding a key value onto the autocomplete which depended on the change which necessitated clearing.
To solve this, I created a hook that watches the value state of the autocomplete and set the value of the input if the checkClear returns true;
function useAutocompleteInputClear(watch, checkClear) {
const elmRef = useRef(null);
useMemo(() => {
if (!elmRef || !elmRef.current) return;
if (!checkClear || typeof checkClear !== "function") return;
const button = elmRef.current.querySelector("button")
if (checkClear(watch) && button) {
button.click();
}
}, [watch])
return elmRef;
}
Its first argument is the value that should be watched and its second argument is a function that returns a boolean. if it is true the clearing will happen.
Also, the hook returns a ref that needs to pass as ref prop to Autocomplete.
const elmRef = useAutocompleteInputClear(value, v => !v || !v.id)
<Autocomplete ref={elmRef}
value={value}
...
using onChange property we can clear the value by clicking the clear icon in the following way
<Autocomplete
fullWidth={true}
label={'Source'}
margin={'noraml'}
multiple={false}
name={'Source'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
ref={SourceRef}
value={formValues.Source === '' ? {label: ''} : {label: formValues.Source}}
options={SourceStatus}
onChange={useCallback((e, v) => {
if (typeof v === 'object' && v !== null) {
handleInputChange(e, v) // help to set the value
} else {
handleInputChange(e, {label: ''}) // help to reset the value
}
})}
/>
In my case for multiselect freeSolo onChange props 3rd argument reason solved my all issues.
AutocompleteChangeReason can be:
blur
clear
createOption
removeOption
selectOption
and 2nd arg of this props gives u already updated list of (multiselect) value/s.
onChange={(_event, newOptions, reason) => {
setOptions(
reason === 'clear' ? [] : [...newOptions.map((o) => Number(o))],
);
}}
If you need only the selected value, set the value to an empty object and render the option to your needs.
<Autocomplete
value={{}}
onChange={handleSelectionChanged}
options={options ?? []}
getOptionLabel={x => (!x ? '' : x?.name ?? '')}
renderInput={params => <TextField {...params} label="" />}
/>
If you are using objects, you can use the following code to clear the field.
Ensure you add isOptionEqualToValue:
<Autocomplete
style={{ width: 250 }}
multiple
id="checkboxes-tags-demo"
options={list}
isOptionEqualToValue={(option, newValue) => {
return option.id === newValue.id;
}}
value={selected}
onChange={(e, val) => handleSelected(e, val)}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
label="Add to Multiple"
placeholder="Favorites" />
)} />
Just set an empty array in your state through functions, and it'll be cleared.
Try this method:
use onChange method and pass third parameter reason and compare to clear text if reason is clear then executed this function.
<Autocomplete
onChange={(event, newValue, reason) => {
if (reason === 'clear') {
console.log("Put your clear logic here: this condition executed when clear button clicked")
setValue({ title: '', year: '' }) //for reset the value
return
}
}}
/>
One easy way to do this is to pass these props to autocomplete like this:
onChange={handleSkillChange}
inputValue=''
clearOnBlur={true}
onChange is an event handler, which stores the value in the state.
inputValue='' helps to ensure that the text field inside autocomplete will always be empty
clearOnBlur={true} helps to clear the value of the autocomplete component when it loses focus.