I'm trying to create a search form with autocomplete suggestions that pulls data from API. I got everything to work in terms of displaying data and selection, but I would like to automatically take the user to the related page once they select one of the suggestions.
<Autocomplete
id="search-input"
freeSolo
disableClearable
options={playerList}
getOptionLabel={(option) => option.nickname}
style={{ width: 200}}
**onClick={console.log("you clicked")}
onSelect={(val)=> window.location.href = "/player-statistics/"+val.nickname+"-"+val.account_id+"-"+server}**
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
if (newInputValue.length >= 3) {fetchPlayers(newInputValue)}
}}
renderInput={(params) => <TextField {...params} label="Search Players" variant="outlined" margin="normal" />}
/>
I have tried with onChange and onSelect, but they are both reloading the page continuously whenever you press the search field or start typing.
The idea is to skip the need to click "search" button.
When you select a suggested name, onInputChange is triggered.
There you need to put the logic for changing the location.
Then the user does not have to click anything else.
If you do not want the page to change when the user only types a single character,
then you need to write logic for that inside onInputChange.
sth. like
// inside onInputChange
if(isASuggestedName(newInputValue)) {
//...change location
}
I figured it out after going through the API.
onChange call has 3 parameters - function(event: object, value: T | T[], reason: string) => void
I basically set it up so relocation is triggered only if reason is select-option and it works perfectly.
Thank you everyone
Related
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)}
/>
)}
/>
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 am trying to use Material-UI Autocomplete as multiple input with react-hook-form and control the defaultValues of the Autocomplete dynamically (render the component prefilled, when editing data, based on already saved data fetched from a database).
So the main question is:
What is the best way to control the default values in an Material-UI Autocomplete component and use it with react-hook-form?
What i did so far:
With using functions and hooks in react, I have wrapped an
Autocomplete component in React Hook Form's Controller to control
the state. I tried to implemented the solutions from the docs of MUI
and react-hook-form and the solutions of the below threads.
I created a minimal Sandbox here
What it does
When i set the defaultValue in Controller, it works to display the controlled default value, but throws me an error: TypeError: Cannot read property 'filter' of undefined
<Controller
as={
<Autocomplete
multiple
value={defaultValues}
onChange={(e, values) => setValue("food", values)}
...
renderInput={params => ( ... )}
/>
}
control={control}
name="food"
defaultValue={defaultValues} // <- this makes the error
/>
When i don't set the defaultValue in Controller, it works perfectly to being able to select multiple values like expected, but it doesn't show the default value.
What is so confusing is, that the Autocomplete has to be controlled with value/onChange and also the Controller has to control with defaultValue/setValue, and it seems they conflict in my case.
It works better when setting defaultValue={ [] } and using a
useEffect and controlling the default value only with
setValue("food", defaultOption);
So i created another Sandbox here
Thanks to Bill's answer i refactored the code to a renderProp
like proposed in the docs:
Yet another sandbox here
Now it works like a charm, but i had to set the onChange prop of
the Autocomplete like this:
onChange={(e, values) => setValue("food", values)}
instead of what the docs proposed to do: (using the passed onChange)
onChange={e => props.onChange(e.something)}
It works, but is this the right way to combine Autocomplete and
react-hook-form?
Compare the questions with these threads:
The main difference to the other threads i am trying to do, is to set the defaultValues of a multiple Autocomplete.
Proper way to use react-hook-form Controller with Material-UI Autocomplete
MUI Autocomplete's 'defaultValue' not working when used with Controller of react-hook-form
Why is initial value not set in Material UI Autocomplete using react-hook-form?
The advised solution in the docs of react-hook-form:
https://react-hook-form.com/api/#Controller
And the Code from Material UI docs:
https://material-ui.com/components/autocomplete/#multiple-values
I was able to get this working by doing the following:
<Controller
name='test'
control={control}
render={({onChange, ...props}) => (
<AutoComplete
{...props}
data-testid='test-select'
width={350}
label='Auto Complete'
onChange={onChange}
options={eventTypes}
getOptionLabel={(option) => option ? option.name : ''}
renderOption={(option) => option.name }
getOptionSelected={(option) => option.name}
renderInput={(params) => (
<TextField {...params} error={error} helperText={helperText} label={label} placeholder={label} />
)}
onChange={(e, data) => onChange(data)}
{...props}
/>
)}
/>
However, I've not found a way to validate this using react-hook-form
You could try custom validation (in case if you are using MUI's Autocomplete with multiple={true}):
<Controller
...
rules={{
validate: (data) => {
if(data.length === 0) return false;
}
}}
/>
Now it works like a charm, but i had to set the onChange prop of the
Autocomplete like this:
onChange={(e, values) => setValue("food", values)}
You can do onChange={(e, newValue) => props.onChange(newValue)}
I have a controlled Autocomplete component bound to a state prop from redux, that's all working. The onChange event is fired when the user clicks the "clear" icon, however the input is focused and the dropdown opens when this occurs.
How can I prevent the input focus / dropdown open only when the selected option is cleared? My preferred behaviour would be for it to return to having "null" selected and showing the label un-shrunk.
The code looks roughly like this. value is sourced from mapStateToProps, onChange comes from mapDispatchToProps.
const LetterSelect: FC<Props> = ({ value, onChange }: Props) => {
return (
<Autocomplete
options={["A", "B", "C", "D"]}
value={value ?? null}
onChange={(e,v) => onChange(v)}
renderInput={params => (
<TextField {...params} label="Letter" variant="outlined" fullWidth />
)}
/>
)
}
Sorry I didn't get back to this sooner: there actually wasn't a direct fix for this.
Instead, I opened an issue on the Github and it was agreed to change the default behaviour - refer to this PR.
Clicking the Clear icon no longer opens the Autocomplete as of v4.8.1
I have been writing a custom Material-UI Select dropdown which has an optional text field at the top to allow the user to search / filter items in the Select if there were many entries.
I am struggling with how to keep the Select open when I click on the text field (rendered as an InputBase) and just have the normal behavior (of closing the Select when a regular MenuItem is selected.
CodeSandbox here : https://codesandbox.io/s/inspiring-newton-9qsyf
const searchField: TextField = props.searchable ? (
<InputBase
className={styles.searchBar}
onClick={(event: Event) => {
event.stopPropagation();
event.preventDefault();
}}
endAdornment={
<InputAdornment position="end">
<Search />
</InputAdornment>
}
/>
) : null;
return (
<FormControl>
<Select
className={styles.root}
input={<InputBase onClick={(): void => setIconOpen(!iconOpen)} />}
onBlur={(): void => setIconOpen(false)}
IconComponent={iconOpen ? ExpandMore : ExpandLess}
{...props}
>
{searchField}
{dropdownElements.map(
(currEntry: string): HTMLOptionElement => (
<MenuItem key={currEntry} value={currEntry}>
{currEntry}
</MenuItem>
)
)}
</Select>
</FormControl>
);
As you can see above I've tried using stopPropagation and preventDefault but to no avail.
check out this codesandbox link: https://codesandbox.io/s/busy-paper-9pdnu
You can use open prop of Select API
I was able to make a controlled open select by providing open prop as a react state variable and implementing correct event handlers. To make it controlled you must provide onOpen and onClose props of the Select and make sure the open prop stays true when the custom textfield is clicked.
One more important thing I had to do was override the default keyDown behavior of the Select component. If you open up a Select and start typing into it, it shifts focus to the select option that matches what you are typing. For example, if you Select had an option with the text Foobar and if you start typing Food and water, it would cause focus to shift from your custom text input onto the Foobar option. This behavior is overridden in the onKeyDown handler of the custom textfield
Working sandbox here
Edit: even though this worked in the codepen, I had to add onChange={handleOpen} to the Select as well to get it working on a real browser with React and Next.
You can still use stopPropagation to make it work
// open state
const [isSelectorOpen, setisSelectorOpen] = useState(false)
// handle change
const handleChange = event => {
const { value } = event.target
event.stopPropagation()
// set your value
}
// selector
<Select
multiple
open={isSelectorOpen}
onChange={handleChange}
input={(
<Input
onClick={() => setisSelectorOpen(!isSelectorOpen)}
/>
)}
// other attribute
>
<MenuItem>a</MenuItem>
<MenuItem>b</MenuItem>
<MenuItem>c</MenuItem>
</Select>
In my case, none of the above worked, but this did the trick to stop closing the Select:
<MenuItem
onClickCapture={(e) => {
e.stopPropagation();
}}>
You can also change onMouseEnter to change some default styling that comes with using MenuItem (like pointer cursor when mouse enters its layout)
onMouseEnter={(e) => {
e.target.style.backgroundColor = "#ffffff";
e.target.style.cursor = "default";
}}
In my case i also needed to remove the grey clicking effect that MenuItem makes on click, which is a new object generated in MuiTouchRipple-root, so changing display to none did the trick.
sx={{
"& .MuiTouchRipple-root": {
display: "none",
},
}}