MUI Autocomplete filterOptions disabled search - reactjs

The problem, when filtering a specific value from my Autocomplete, it indeed filters it but also the search capability no longer works on the autocomplete.
Here is the code:
<Autocomplete
fullWidth
// needed to remove mui error related to groupBy
options={sortedDimensionTypes}
// filter out the empty option
filterOptions={(options) => options.filter((option) => option.id !== "-1")}
getOptionLabel={(option) => option?.data?.label}
isOptionEqualToValue={(option, value) => option?.id === value?.id}
groupBy={(option) => optionsGroupLabel(option.data.type)}
size="small"
onChange={(_, newValue, reason) => {
switch (reason) {
case "clear":
setFilters((prevFilters) => {
const newFilters = prevFilters.slice();
newFilters.splice(currentIndex, 1, getBaseAttributionFilter());
return newFilters;
});
handleSelectOption(getBaseMetadataOption());
break;
case "selectOption":
setFilters((prevFilters) => {
const newFilters = prevFilters.slice();
const fltr = filterBase(newValue as MetadataOption);
newFilters.splice(currentIndex, 1, fltr);
return newFilters;
});
handleSelectOption(newValue as MetadataOption);
break;
}
}}
openOnFocus
// disable clearable when no option selected.
disableClearable={selectedType.id === "-1"}
renderInput={(params: AutocompleteRenderInputParams) => (
<TextField {...params} label={attributionText.DIMENSION_TYPE} variant="outlined" margin="dense" />
)}
value={selectedType}
/>
Expected behavior: it would simply filter out the option without an id (where the id equals -1) and allow searching on the rest.
Actual Behavior: typing a search does nothing.
Here is a simple recreation with a basic example from the MUI examples.
https://stackblitz.com/edit/react-yzmpeh?file=demo.tsx

Related

MUI Custom groupBy

I have built MUI grouped labels, but the moment I try to add the element into the state, the application crashes. Here's the minimal code.
const options = labels.map(option => {
return {
type: JSON.parse(localStorage.getItem("recentTags") as string).includes(option) ? "RECENT" : "ALL ",
labelText: option
};
});
<Autocomplete
multiple
disableClearable
filterSelectedOptions
groupBy={option => option.type}
isOptionEqualToValue={(option, value) => {
const labelText = value.labelText ? value.labelText : value;
return option.labelText.toUpperCase() === labelText.toUpperCase();
}}
value={value}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
onChange={(_event, newValue) => {
console.log(newValue.map(el => el));
const latestLabel: any = newValue.slice(-1).pop();
const prevLabels = JSON.parse(localStorage.getItem("recentTags") as string);
if (!prevLabels.includes(latestLabel.labelText) && prevLabels.length < 5) {
localStorage.setItem("recentTags", JSON.stringify([...prevLabels, latestLabel.labelText]));
}
const newLabel = newValue.filter((x) => !labels.includes(x))[0];
setValue(newValue);
onSaveEcardLabels!(newValue, ecardItem.id);
if (!!newLabel) {
labels.push(newLabel.labelText);
}
}}
/>
I am storing recently used tags in localstorage, but I am confused in it's logic as well, I am not able to replace any newly recently used tag in local storage. I believe the problem lies in onChange event.
While onChange in newValue I get previous index and current Object, I would like only single array with index i.e. labelText.

#material-ui Autocomplete: set input value programmatically

I have an asynchronous Autocomplete component that works fine so far.
Hopefully the simplified code is understandable enough:
export function AsyncAutocomplete<T>(props: AsyncAutocompleteProps<T>) {
const [open, setOpen] = useState(false);
const [options, setOptions] = useState<T[]>();
const onSearch = (search: string) => {
fetchOptions(search).then(setOptions);
};
return (
<Autocomplete<T>
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
onChange={(event, value) => {
props.onChange(value as T);
}}
getOptionSelected={props.getOptionSelected}
getOptionLabel={props.getOptionLabel}
options={options}
value={(props.value as NonNullable<T>) || undefined}
renderInput={(params) => (
<TextField
{...params}
onChange={(event) => onSearch(event.currentTarget.value)}
/>
)}
/>
);
}
The component above works easily: when the user clicks on the input, the Autocomplete component displays an empty input field where the user can type in a value to search for. After the input has changed, the options are refetched to show matching results.
Now I want to add support for shortcodes: when the user types qq, the search term should be replaced by something, just like if the user would have typed something himself.
However, I found no way to update the value of the rendered TextField programmatically. Even if I set value directly on the TextField, it won't show my value but only the users input.
So, any ideas how to solve this problem?
Thank you very much.
What I've tried so far was to simply update the input within onKeyUp:
// ...
renderInput={(params) => (
<TextInput
{...params}
label={props.label}
onChange={(event) => onSearchChange(event.currentTarget.value)}
InputProps={{
...params.InputProps,
onKeyUp: (event) => {
const value = event.currentTarget.value;
if(value === 'qq') {
event.currentTarget.value = 'something';
}
},
}}
/>
)}
With the code above I can see the something for a short time, but it gets replaced by the initial user input very soon.
Autocomplete is useful for setting the value of a single-line textbox in one of two types of scenarios: combobox and free solo.
combobox - The value for the textbox must be chosen from a predefined set.
You are using it so it not allowing you to add free text (onblur it replaced)
Answer: To take control of get and set value programmatically.
you need a state variable.
Check here codesandbox code sample taken from official doc
Your code with my comment:-
export function AsyncAutocomplete<T>(props: AsyncAutocompleteProps<T>) {
... //code removed for brevity
//This is a state variable to get and set text value programmatically.
const [value, setValue] = React.useState({name: (props.value as NonNullable<T>) || undefined});
return (
<Autocomplete<T>
... //code removed for brevity
//set value
value={value}
//get value
onChange={(event, newValue) => setValue(newValue)}
renderInput={(params) => (
<TextInput
{...params}
label={props.label}
onChange={(event) => onSearchChange(event.currentTarget.value)}
InputProps={{
...params.InputProps,
onKeyUp: (event) => {
//get value
const value = event.currentTarget.value;
//if qq then set 'something'
if (value === "qq") {
setValue({ name: "something" });
}
//otherwise set user free input text
else {
setValue({ name: value });
}
},
}}
/>
)}
/>
);
}

Cancel MUI Autocomplete onChange when reason is "create-option"

I want to be able to not allow a user to add an input value to Autocomplete's values array if an error exists with that value, but I also would like to preserve the input value currently being typed. Is there a way to cancel the event?
<Autocomplete
multiple
options={[]}
freeSolo
onChange={(event, value, reason) => {
if (reason === 'create-option' && error) {
// prevent this event from going through
}
}}
...
/>
Appreciate the help!
This isn't a solution to the question asked above per se, but solves my problem.
const [inputValue, setInputValue] = useState('');
return (
<Autocomplete
multiple
options={[]}
freeSolo
renderTags={(values: string[], getTagProps) => {
return values.map((option: string, index: number) => {
if (!option) return;
return (
<Chip
variant="outlined"
label={option}
{...getTagProps({ index })}
/>
);
});
}}
inputValue={inputValue}
onChange={(event: any, values: any, reason: string) => {
if (reason === 'create-option' && form.getState().errors.verses) {
setInputValue(values[values.length - 1]);
values[values.length - 1] = null;
} else {
setInputValue('');
}
}}
/>
If you set inputValue on Autocomplete and then set the persisted value to null, you can preserve the input value without saving the undesired value so the user won't have to start typing all over again. If the value is valid, then set the input value to empty string.

Selected Value not showing in Textfield Select - Material UI React Component

I have TextField Select Material UI components based on a certain number of value in a variable.
{this.state.selectedNextHops.map((nextHop, index) => (
<div>
<TextField
select
className="vnfprofile-field"
InputProps={{ className: 'variable-value site-details-view-textfield' }}
InputLabelProps={{ shrink: true }}
SelectProps={{
MenuProps: {
className: 'vnf-designer-value',
getContentAnchorEl: null,
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left',
}
},
}}
value = {this.state.selectedNextHops[index] || ''}
disabled={this.props.newPopoverPanelView === 'VIEW' ? true : false}
onChange={(e) => this.handleChange('nexthop', e)}
>
{this.state.remainingNextHops.length !== 0 ? this.state.remainingNextHops.map((option, i) => (
<MenuItem key ={i} value = {option || ''}>
{option}
</MenuItem>
)) :
<MenuItem value = {'No Data Available'}>
{'No Data Available'}
</MenuItem>}
</TextField>
<TextField
className="vnfprofile-field subnet-textfield"
InputProps={{ className: 'variable-value' }}
InputLabelProps={{ shrink: true }}
value = {'29'}
/>
</div>
))
}
The TextFields show up sequentially when I select value from the previous dropdown and filters the menu based on previous selection.
if(selectedNextHops.indexOf(event.target.value) === -1) {
selectedNextHops.push(event.target.value);
}
remainingNextHops = this.props.nextHopSapds.filter(nextHop => selectedNextHops.indexOf(nextHop) === -1);
this.setState({
selectedNextHops: selectedNextHops,
remainingNextHops: remainingNextHops
});
Update: Here is my handleChange Method ->
handleChange(type, event) {
let selectedNextHops = JSON.parse(JSON.stringify(this.state.selectedNextHops));
let remainingNextHops = [];
if(type === 'nexthop') {
selectedNextHops = selectedNextHops.filter(nh => nh !== '');
isContentChanged = true;
if(selectedNextHops.indexOf(event.target.value) === -1) {
selectedNextHops.push(event.target.value);
}
remainingNextHops = this.props.nextHopSapds.filter(nextHop => selectedNextHops.indexOf(nextHop) === -1);
if(remainingNextHops.length !== 0) {
selectedNextHops.push('');
}
this.setState({
selectedNextHops: selectedNextHops,
remainingNextHops: remainingNextHops
});
}
}
The state is updating fine, but the textfield does not display the selected value. I have tried everything I knew. Any help is appreciated.
This is hard to debug without seeing a working snippet or the state ( especially this.state.selectedNextHops) , but based on the code sandbox provided ( in the comment ) , I assume it's the same problem, so this answer will apply to the sandbox code :
this.setState({
selectedID: event.target.value.id,
visibleValue: event.target.value.name
});
event.target.value.id and event.target.value.name are undefined,
console.log(console.log(event.target)) // {value: "S0002", name: undefined}
For the select to display a selected option, the value attribute for both need to match :
<select value="2">
^^^^^^^^^
<option value="1">first value</option>
<option value="2">second value</option>
^^^^^^^^^
</select>
in the example in the code sandbox, the value of the Select is value={this.state.visibleValue} and the values of the options are value={x.label}
Since this.state.visibleValue is always undefined, you'll never see the value of the select update.
A quick fix for this is to change the handleChage function to :
handleChangeTest = event => {
this.setState({
selectedID: event.target.id,
visibleValue: event.target.value
});
};
but that will leave selectedID undefined , to set it, add the attribute id={x.id} to the option and use event.currentTarget to get its value :
{this.state.data.map(x => (
<MenuItem key={x.id} value={x.label} id={x.id}>
^^^^^^^^^
{x.name}
</MenuItem>
))}
And
handleChangeTest = event => {
this.setState({
selectedID: event.currentTarget.id,
^^^^^^^^^^^^^^^^^^^^^^
visibleValue: event.target.value
});
};
Working SandBox
So you try to access the key with e.target.value.id but the target object has only the value and not the id itself. That is why it is undefined after you call the handleChange method. There is a way to access the key though:
The callback does not only pass the event but also the child object as second parameter and this can be used to get the key like this:
handleChangeTest = (event, child) => {
this.setState({
selectedID: child.key,
visibleValue: event.target.value
});
};
This will set the key as selectedID and the value of the selected item as visibleValue.

Keep Semantic-React multiple selection dropdown open when removing an item

I'm trying to keep the multiple selection dropdown available from Semanti-UI-React open when I remove a selected item, but still have it close onBlur. I can set closeOnBlur to false and I get the behavior I want for removing the selected item, but then I have to click the arrow on the dropdown to close it. I tried plugging into the relevant events to see if there was a way I could achieve the desired behavior manually, but oddly enough if closeOnBlur is set to false, I don't even get the onBlur event.
Here is my code:
<Dropdown
style={{whiteSpace: 'nowrap', zIndex: 9999}}
upward={true}
search={true}
multiple={true}
selection={true}
loading={loading}
options={options}
value={this.props.selectedCodes || []}
closeOnBlur={false}
onBlur={() => console.log('onBlur')}
onChange={ (e, d) => {
console.log('onChange');
const value = d.value as string[];
const displayValues = value.map(code => {
const lookupEntry = options.find(i => i.value === code);
return lookupEntry ? lookupEntry.text : '';
});
const displayValue = displayValues.reduce(
(result, text) => {
return `${result}, ${text}`;
}, ''\
);
this.props.onSelectionChange(value, displayValue);
}}
onClose={() => console.log('onClose')}
/>
Any suggestions on how to achieve my desired behavior, or insight as to why the onBlur even doesn't fire if closeOnBlur is set to false?
This component supports manual control over it's open/closed status view an open prop. So simply manage that prop via the state of your custom containing class;
getInitialState() {
return { open: false };
},
handleClose() {
this.setState({open: false});
},
handleOpen() {
this.setState({open: true});
}
render() {
return <Dropdown
open={this.state.open}
onBlur={this.handleClose}
onFocus={this.handleOpen}
...
/>;

Resources