MUI Autocomplete with FreeSolo not detecting value of onInputChange - javascript

I'm using MUI's Autocomplete component with freeSolo so that the user may either select an option from the dropdown or input a value into the textfield. I am able to obtain the value of both: when the user selects and types in an arbitrary value.
However, it's only detecting the value onChange, not onInputChange. I need to distinguish between the value being selected from the options or entered as free text. I need to be able to prompt the user that they are submitting a unique option.
This is the basic MUI Autocomplete component:
export default function ControllableStates() {
const [value, setValue] = React.useState(null);
const [inputValue, setInputValue] = React.useState('');
return (
<div>
<div>{`InputValue: '${inputValue}'`}</div>
<div>{`Value: '${value}'`}</div>
<br />
<Autocomplete
freeSolo
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={data.map((option) => option.name)}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" />}
/>
</div>
);
}

Related

How can I prevent my datepicker getting re-focussed after clicking another input

I have a Datepicker of Material that opens if you click on the textfield it belongs to. If you try to select another textfield while the datepicker is opened it will target/focus back to the datepicker.
Note: This issue is also for targeting nothing, it will refocus the datepicker again after closing it.
Example Demo: https://stackblitz.com/edit/react-b4xpki?file=demo.tsx
Code:
`
export default function BasicDatePicker() {
const [value, setValue] = React.useState<Dayjs | null>(null);
const [open, setOpen] = React.useState(false);
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<TextField placeholder='Click here while datepicker is opened' fullWidth={true} sx={{mb:2, mr: 2}}/>
<DatePicker
label="Basic example"
value={value}
onChange={(newValue) => {
setValue(newValue);
}}
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
renderInput={(props) => (
<TextField {...props} onClick={(e) => setOpen(true)} />
)}
/>
</LocalizationProvider>
);
}
So how to reproduce to problem?
Click op the datepicker (not the icon)
Click on another textfield
Watch how the target/focus is back on the datepicker
You can create focus named state so can control in onFocus at <TextField />
firstly, create focus named state:
const [focus, setFocus] = useState(false);
TextField: if open is false in onFocus, currentTarget shouldn't be focus.
<TextField
{...props}
onFocus={(e) => {
setFocus(open);
if (!open) {
e.currentTarget.blur();
return;
}
}}
focused={focus}
onClick={(e) => setOpen((prev) => !prev)}
/>
if the value changes in DatePicker, focus should be true.
<DatePicker
label="Basic example"
value={value}
onChange={(newValue) => {
setValue(newValue);
setFocus(true);
}}
...otherProps
/>
link: https://stackblitz.com/edit/react-b4xpki-rirdxn?file=demo.tsx

Checking checkbox inside Autocompletion with MUI

[
{ label: 'First', checked: false },
{ label: 'Second', checked: true }
]
Here is a very short snippet of how the data could look like.
I am using Material UI's Autocomplete, to make it possible for searching on labels.
These labels are having a checkbox.
Problem is, I can't use onChange on <Checkbox /> when its a renderOption
The Autocomplete just closes, without doing any action
<Autocomplete
disableCloseOnSelect={true}
options={array}
getOptionLabel={option => option.label.toString()}
renderInput={params => (
<TextField
{...params}
fullWidth
label="Select label"
variant="outlined"
error={false}
/>
)}
renderOption={opt => (
<div>
<Checkbox
checked={opt.checked}
onChange={() => alert('not being fired...')}
/>
<p>{opt.label}</p>
</div>
)}
/>
You shouldn't use onChange in Checkbox, You should use onChange prop of Autocomplete, and set default value by value prop
and Checkbox receive its props from renderOption's props
const [selectedOptions, setSelectedOptions] = useState([]);
useEffect(() => {
setSelectedOptions(options.filter(op => op.checked));
}, []);
<Autocomplete
options={options}
value={selectedOptions}
onChange={(event, newValue) => {
setSelectedUsers(newValue);
}
renderOption={(props, option, { selected }) => (
<div {...props}>
<Checkbox checked={selected} />
<p>{option.name}</p>
</div>
)}
disableCloseOnSelect
/>
onChange event will work in Checkbox provided in renderOption function but it only works when you click on checkbox not anywhere else on list item. You can use onClick on the parent div if that fulfills your purpose.

How can clear React Material AutoComplete selected label clear if state empty?

I have a state :
const [searchEntryNo, setSearchEntryNo] = useState('');
Then I have a function to clear state like that.
const handleClear = () => {
setSearchEntryNo('');
};
Then There React AutoComplete :
<Autocomplete
className={classes.searchBox}
id="combo-box-demo"
size="small"
options={entryList}
getOptionLabel={(option) => option}
onChange={(event, newValue) => {
setSearchEntryNo(newValue);
}}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} variant="outlined"
placeholder="Searching" size="small" />}
/>
Note: entryList is the a array state
There is a button where handleClear() function fired the button click:
<Button onClick={handleClear}> Clear </Button>
Now I want to clear selected label from AutoComplete Box after handleClear() fired.
How can I solve the issue?
Please Help me!!
onChange also passes a second parameter to the change handler: docs
onChange={(_, value: any, reason: string) => {
if (value) {
// seState
}
if (reason === "clear") {
// clear State
}
}}

Select all and Select None buttons in Autocomplete Material UI React

I want to implement two buttons Select All and Select None inside Autocomplete React Material UI along with checkbox for each option.When Select All button is clicked all the options must be checked and when I click Select None all the options must be unchecked.
How do I implement that ?
<Autocomplete
id={id }
size={size}
multiple={multiple}
value={value}
disabled={disabled}
options={items}
onChange={handleChange}
getOptionLabel={option => option.label}
renderOption={(option, { selected }) => (
<React.Fragment >
{isCheckBox(check, selected)}
{option.label}
</React.Fragment>
)}
renderInput={params => (
<TextField id="dropdown_input"
{...params} label="controlled" variant={variant} label={label} placeholder={placeholder} />
)}
/>
export function isCheckBox(check, selected) {
if (check) {
const CheckBox = <Checkbox
id="dropdown_check"
icon={icon}
checkedIcon={checkedIcon}
checked={selected}
/>
return CheckBox;
}
return null;
}
I stumbled into the same issue earlier today.
The trick is to use local state to manage what has been selected, and change the renderOption to select * checkboxes if the local state has the 'all' key in it.
NB: At the time of writing React 16 is what I'm working with
I'm on a deadline, so I'll leave a codesandbox solution for you instead of a rushed explanation. Hope it helps :
Select All AutoComplete Sandbox
Updated
for React version 16.13.1 and later. codesandbox
const [open, setOpen] = useState(false);
const timer = useRef(-1);
const setOpenByTimer = (isOpen) => {
clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
setOpen(isOpen);
}, 200);
}
const MyPopper = function (props) {
const addAllClick = (e) => {
clearTimeout(timer.current);
console.log('Add All');
}
const clearClick = (e) => {
clearTimeout(timer.current);
console.log('Clear');
}
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
return (
<Autocomplete
PopperComponent={MyPopper}
onOpen={(e) => {
console.log('onOpen');
setOpenByTimer(true);
}}
onClose={(obj,reason) => {
console.log('onClose', reason);
setOpenByTimer(false);
}}
open={open}
.....
....
/>
);
Old Answer
Just customise PopperComponent and do whatever you want.
Autocomplete API
const addAllClick = (e: any) => {
setValue(items);
};
const clearClick = (e: any) => {
setValue([]);
};
const MyPopper = function (props: any) {
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
<Autocomplete
PopperComponent={MyPopper}
...
/>
If you want to make an autocomplete with select all option using react material ui and react hook form, you can implement to Autocomplete like so
multiple: To allow multiple selection
disableCloseOnSelect: To disable the close of the box after each selection
options: Array of items of selection
value: Selected options.
getOptionLabel: The string value of the option in our case is name
filterOptions: A function that determines the filtered options to be rendered on search, in our case we used it to add selectAll checkbox.
renderOption: Render the option, use getOptionLabel by default.
renderInput: To render the input,
onChange: Callback fired when the value changes
Now you can play with selected values using handleChange so once the select is fired check if the selected option is select all if yes then set the newest selectedOptions
<Autocomplete
multiple
disableCloseOnSelect
options={items}
value={selectedOptions}
getOptionLabel={(option) => option.name}
filterOptions={(options, params) => {
const filtered = filter(options, params)
return [{ id: 0, name: selectAllLabel }, ...filtered]
}}
renderOption={(props, option, { selected }) => {
// To control the state of 'select-all' checkbox
const selectAllProps =
option.name === 'Sélectionner Tous' ? { checked: allSelected } : {}
return (
<li {...props}>
<Checkbox checked={selected} {...selectAllProps} />
{option.name}
</li>
)
}}
renderInput={(params) => (
<TextField {...params} label={label} placeholder={label} />
)}
onChange={handleChange}
/>
you can refer to the Autocomplete API to get detailed definition of each item
You can refer to this codeSendBox to check a demo of react material Autocomplete with select all using react material ui version 5 and react hook form verion 7

Material ui Autocomplete press enter to create new chips

I wish I could do such a thing using Autocomplete of material ui: wertarbyte
That is, inserting text (string) without having a list of elements from which you must select.
Therefore the noOptions message should not appear, every time the enter key is pressed on the keyboard the text is inserted.
Link: codesandbox
Code:
import React from "react";
import Chip from "#material-ui/core/Chip";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
const useStyles = makeStyles(theme => ({
root: {
width: 500,
"& > * + *": {
marginTop: theme.spacing(3)
}
}
}));
export default function Tags() {
const classes = useStyles();
return (
<div className={classes.root}>
<Autocomplete
multiple
id="tags-outlined"
options={[]}
defaultValue={["foo", "bar"]}
//getOptionLabel={(option) => option}
//defaultValue={[top100Films[13]]}
//filterSelectedOptions
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
</div>
);
}
In case you have simple elements (not objects, just strings), and you don't really need to handle state (your autocomplete is not controlled) you can use the freeSolo prop of the Autocomplete.
<Autocomplete
multiple
freeSolo
id="tags-outlined"
options={["foo", "bar"]}
defaultValue={["foo", "bar"]}
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
In case your elements are more complex and you do need to control the element:
Make sure the Autocomplete tag is a controlled one (you manage to value).
Listen to key down event on the TextField.
If the code is Enter (e.code === 'Enter') - take the value of the input and push it to the list of the current values that you have.
Make sure you also handle the onChange to handle the removal of elements:
Here is the code:
const [autoCompleteValue, setAutoCompleteValue] = useState(["foo", "bar"]);
return (
<Autocomplete
multiple
id="tags-outlined"
options={[]}
value={autoCompleteValue}
onChange={(e, newval, reason) => {
setAutoCompleteValue(newval);
}}
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
onKeyDown={e => {
if (e.code === 'enter' && e.target.value) {
setAutoCompleteValue(autoCompleteValue.concat(e.target.value));
}
}}
/>
)}
/>
);
Check the live working example of both options: https://codesandbox.io/s/mui-autocomplete-create-options-on-enter-gw1jc
For anyone who wants to input the current best match on enter key press (as opposed to any custom text) you can use the autoHighlight prop.
<Autocomplete
multiple
autoHighlight
id="tags-outlined"
options={["foo", "bar"]}
defaultValue={["foo", "bar"]}
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
To do this, don't use the Autocomplete element from MUI. Just use a a standard TextField with the use of InputProps. All you need to do is add a onKeyDown listener to the TextField that listens for 'Enter' and when the function is triggered, have it add to an array of Chips in the InputProps. It might look something like this:
const [inputValue, setInputValue] = useState('');
const [chips, setChips] = useState([])
const inputChange = ({target: {value}}) => {setInputValue(value)};
const handleKeyDown = ({key}) => {
if(key === 'Enter') {
setChips([...chips, inputValue])
}
};
<TextField
fullWidth
variant="outlined"
label="Fish and Chips"
value={inputValue}
onChange={inputChange}
multiline
InputProps={{
startAdornment: chips.map((item) => (
<Chip
key={item}
label={item}
/>
)),
}}
/>
This is untested as written here, but it should work. I've done something similar in one of my apps.

Categories