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 })}
Related
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.
I am trying to default check the first radio button which the following code helps me to do. When loaded the page the first radio button is checked but the problem i am facing is that it doesn't allow me to check the other buttons that also are present in the array.
constructor(props: any) {
super(props);
this.state = {
selectedSort: '',
sort: ['Apple', 'Orange '],
}
}
this.state.sort.map((sortType:string, index:number) => {
return <span key={`${sortType}${index}` onClick={() => this.setSort(sortType)} >
<input type="radio" id={sortType}
value={this.state.selectedSort}
name={sortType} defaultChecked={index===0}
}/>
<span>{sortType}</span>
})
private setSort = (selectedSort: string) => {
this.setState({
selectedSort: selectedSort
});
}
Issue
The defaultChecked value is a boolean but your condition sortType === 0 will always evaluate false since your sortType is only ever one of your sort state values, i.e. ["Apple", "Orange "].
Solution
If you want the first radio button to be default checked then you should compare against the mapped index.
defaultChecked={index === 0}
Other Issues & Suggestions
Radio button group inputs should all have the same name attribute.
Use a semantic label to wrap your inputs so they are more accessible.
Use the radio input's onChange event callback versus an onClick, to update state.
The sortType values alone should be sufficient for a React key.
Code:
{this.state.sort.map((sortType, index) => (
<label key={sortType}>
<input
type="radio"
id={sortType}
value={selectedSort}
name="sortType"
defaultChecked={index === 0}
onChange={(e) => this.setState({ selectedSort: e.target.id })}
/>
{sortType}
</label>
))}
Additionally, I suggest converting this to a fully controlled input since you have already all the parts for it. Remove the value attribute and use the checked prop. Set what you want the initial checked state to be. This will allow you have have already valid checked state.
state = {
selectedSort: 'Apple',
sort: ['Apple', 'Orange '],
}
{this.state.sort.map((sortType, index) => (
<label key={sortType}>
<input
type="radio"
id={sortType}
name="sortType"
checked={sortType === this.state.selectedSort}
onChange={(e) => this.setState({ selectedSort: e.target.id })}
/>
{sortType}
</label>
))}
Demo
I am working with React and material-ui.. I just realize i have a warning with the Autocomplete component when i try to submit the form, so i tried to do something really basic just like in the documentation:
let Form = props => {
return(
<form noValidate onSubmit={handleSubmit} >
<Autocomplete
id="combo-box-demo"
options={[{id:1,name:"test"},{id:2, name:"test2"}]}
getOptionLabel={(option) => option.name}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Combo box" variant="outlined" />}
/>
and when i try to submit the form i get the following error:
Material-UI: The value provided to Autocomplete is invalid.
None of the options match with {"id":1,"name":"test"}.
You can use the getOptionSelected prop to customize the equality test.
I also realize that if i set the options in the state of the component there is no warning (just when they are set like a constant). So i wonder if some of you have any idea of this behavior? thank you so much in advance.
Basically the reason why you get the warning is a default implementation of getOptionSelected in version 4.x.x:
getOptionSelected = (option, value) => option === value
In your case, selecting a value the following comparison happens:
// option === value:
{id:1, name:"test"} === {id:1, name:"test"} // false
Obviously, it can be true in some circumstances. In this particular case, it's false because of objects pointing to the different instances.
Solution! You have to overwrite getOptionSelected implementation:
<Autocomplete
getOptionSelected={(option, value) => option.id === value.id}
...otherProps
/>
[Update]
Note, in version 5.x.x the prop was renamed:
- getOptionSelected={(option, value) => option.id === value.id}
+ isOptionEqualToValue={(option, value) => option.id === value.id}
version 5.0
isOptionEqualToValue={(option, value) => option.value === value.value}
Also when you want to build a searcher where value you write is not necesary the same as the options you can set freeSolo to true and the warning will disapear
This Worked,
getOptionSelected={(option, value) => option.value === value.value}
https://github.com/mui-org/material-ui/issues/18514#issuecomment-606854194
Following up on elVengadors Answer:
When you want to build a searcher where the value you type in the box (the inputValue) is not necessarily one of the provided options you can set freeSolo to true.
This will stop the warning message from being displayed.
The need for this might arise if you are creating a component that allows for asynchronous querying of an API. This would cause the value of options to change based on the response from the API, but options that have already been selected before changing the inputValue to query the API may not be included in this new list of options.
In the Autocomplete Component Documentation, freeSolo is described as:
If true, the Autocomplete is free solo, meaning that the user input is not bound to provided options.
Bonus
Setting freeSolo to true will remove the pop-up button (the drop-down arrow on the right side of the Autocomplete component). To retain this button, you should also add forcePopupIcon={true}.
I had same problem after adding getOptionSelected error gone.
Error:
<Autocomplete
fullWidth={true}
label={'Location'}
margin={'noraml'}
multiple={false}
name={'location'}
value={formValues.location === '' ? {label: ''} : {label: formValues.location}}
options={location}
ref={locationRef}
onChange={useCallback((e, v) => handleInputChange(e, v))}
/>
Solution: getOptionSelected property added
<Autocomplete
fullWidth={true}
label={'Location'}
margin={'noraml'}
multiple={false}
name={'location'}
getOptionSelected={useCallback((option, value) => option.value === value.value)} // added
value={formValues.location === '' ? {label: ''} : {label: formValues.location}}
options={location}
ref={locationRef}
onChange={useCallback((e, v) => handleInputChange(e, v))}
/>
I think you should not use <form> to wrap AutoComplete component. You should set value for AutoComplete and use a function to handle on click button to submit.
Try this:
let Form = props => {
const [value, setValue] = useState({})
const handleOnSubmit = (value) => {
setValue(value)
...
}
return(
<div>
<Autocomplete
id="combo-box-demo"
value={value}
options={[{id:1,name:"test"},{id:2, name:"test2"}]}
getOptionLabel={(option) => option.name}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Combo box" variant="outlined" />}
/>
<Button onClick={() => handleOnSubmit(value)}>Submit</Button>
</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.
I have integrated React-Select (multi) with redux-form. It works if you select a value and submit. However if you need to submit the form again (ie. they had an invalid field) the React-Select(multi) has an array with undefined values.
<Field
ref={ref => this.refTest = ref}
className = "form-control"
name="categories"
options={
this.state.categories.map(c => {
return { value: c.id, label: c.categoryName };
})
}
onBlur={this.onBlur}
component={multiSelect}
/>
The multiSelect is using this component:
export const multiSelect = (field) => {
console.log(field);
return(
<div>
<Select
name={field.name}
{...field.input}
className="section"
multi={true}
options={field.options}>
</Select>
{field.meta.touched && field.meta.error &&
<label id="basic-error" className="validation-error-label">This field is required.</label>}
</div>
);
};
No matter what I do, the second "Submit" click always has an array with undefined for "category".
After some serious debugging - it was due to how I was performing handleInitialise(). ReduxForm expects arrays of values to be in a specific format, specifically:
[{value: "1", label: "Test"}, ...]
Anyone experiencing 'undefined' values pay careful attention to how you are setting and retrieving values. Avoid mutating the object in both cases as I think this lead to some strange behaviour.