MUI Autocomplete's 'defaultValue' not working when used with Controller of react-hook-form - reactjs

I am trying to use MUI's Autocomplete with react-hook-form. I have wrapped an Autocomplete component in React Hook Form's Controller. When I try to set defaultValue to AutoComplete it does not work, when I try to change the preset value the Autocomplete component breaks.
Here is the snippet from my code.
<Controller
name="combo-box-demo"
control={control}
defaultValue={top100Films.find(film => film.year === selectedFilmYear)}
as={
<Autocomplete
id="combo-box-demo"
options={top100Films}
getOptionLabel={option => option.title}
style={{ width: 300 }}
renderInput={params => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
}
/>
Sandbox link of working demo code is here.

You should add an onChange prop on the Controller and return the selected object value
Also you can then implement getOptionSelected AutoComplete
export default function ComboBox() {
const { control } = useForm({});
const [selectedFilmYear, setSelectedFilmYear] = React.useState(1994);
return (
<Controller
name="combo-box-demo"
control={control}
defaultValue={top100Films.find(film => film.year === selectedFilmYear)}
onChange={([val, obj]) => obj}
as={
<Autocomplete
id="combo-box-demo"
options={top100Films}
getOptionSelected={(obj, newval) => obj.name === newval.name}
getOptionLabel={option => option.title}
style={{ width: 300 }}
renderInput={params => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
}
/>
);
}

Related

Create tag using Autocomplete material UI when user input anything

I can type something which shows selected tags from the dropdown list but I want a user can type something and create a tag or multiple tags separated by a comma.
I used a useState hook which is an array.
const [tags, setTags] = useState([]);
I set the Autocomplete like the following code -
<Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={tags}
defaultValue={[]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
placeholder="Tags"
value={tags}
onChange={(e) => setTags([...tags, e.target.value.split(",")])}
/>
)}
/>;
Surprisingly, I tried for an hour before questioning. But Solve it within a few moments after putting the question.
Here's the solution-
<Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={tags}
defaultValue={[...tags]}
freeSolo
autoSelect
onChange={(e) => setTags([...tags, e.target.value])}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
placeholder="Tags"
value={tags}
/>
)}
/>;
the output will look like this.
user input tag
but I want to add multiple tags that didn't happen right now which can be put via giving a space or comma.
Partly relevant but I think it can be helpful for someone using React Hook Form library with MUI5.
Storing tags in the state would render them being saved in form data when submitted. Instead, you need to use their onChange function.
const Tags = ()=>{
const { control,handleSubmit } = useForm({
defaultValues: {
checkbox: true,
autocomplete:["test"]
}
});
const onSubmit = data => console.log(data);
return <>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="autocomplete"
control={control}
render={({ field }) => {
const {value,onChange} = field
return <Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={value}
defaultValue={[...value]}
freeSolo
autoSelect
onChange={((e)=>onChange([...value,e.target.value]))}
renderInput={(params) => {
return <TextField
{...params}
label="Tags"
placeholder="Tags"
value={value}
/>
}}
></Autocomplete>
}
}
/>
<input type="submit" />
</form>
</>
}

I can't get new value from autocomplete react

I am using autocomplete of material UI in my react app, I want to get the value when I change the item but I am getting undefined.
Here is my code
<Controller
name="partnerId"
control={control}
defaultValue={[]}
render={({ field: { onChange, value } }) => (
<Autocomplete
id="filter-demo"
className="mt-8 mb-16"
defaultValue={personObj}
value={value}
onChange={(newValue) => {
console.log('value...', newValue)
onChange(newValue);
}}
variant="outlined"
options={partners}
getOptionLabel={option => option.name}
filterOptions={filterOptions}
renderInput={params => <TextField {...params} label="Partners" variant="outlined" />}
/>
)}
/>
Try onchange as
onChange={(event,newValue) => {
console.log('value...', newValue)
onChange(newValue);
}}

A component is changing an uncontrolled Autocomplete to be controlled

Can you tell me that why I'm getting error "A component is changing an uncontrolled Autocomplete to be controlled.
Elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled Autocomplete element for the lifetime of the component."
component :
function AutoComplete(props) {
const defaultProps = {
options: props.options,
getOptionLabel: option => option.name,
};
const handleChange = (e, value) => {
props.onChange(value);
};
return (
<Autocomplete
{...defaultProps}
renderInput={params => (
<TextField {...params} label={props.label} margin="normal" />
)}
onChange={handleChange}
value={props.value}
/>
);
}
calling autocomplte:
<Controller
control={control}
name = 'country'
as = {
<AutoComplete
options={countryOptions}
onChange={selectCountryHandler}
label="Country"
value={selectedCountry || ''}
/>
} />
how can I solve this error?
You ensured that the value property never had been undefined, but you had to do same for inputValue.
the "value" state with the value/onChange props combination. This state represents the value selected by the user, for instance when pressing Enter.
the "input value" state with the inputValue/onInputChange props combination. This state represents the value displayed in the textbox.
⚠️ These two state are isolated, they should be controlled independently.
Component becomes uncontrolled when inputValue property is undefined, and vice versa.
If in the following example you delete an empty string from
React.useState('') you'll get the same error message because inputValue during first render is undefined.
import React from 'react'
import TextField from '#material-ui/core/TextField'
import Autocomplete from '#material-ui/lab/Autocomplete'
const options = ['Option 1', 'Option 2']
export default function AutocompleteLab() {
const [value, setValue] = React.useState(options[0])
const [inputValue, setInputValue] = React.useState('')
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : 'null'}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
value={value}
onChange={(_, newValue) => {
setValue(newValue)
}}
inputValue={inputValue}
onInputChange={(_, newInputValue) => {
setInputValue(newInputValue)
}}
options={options}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Name" variant="outlined" />}
/>
</div>
)
}
When no value is selected, you need to add || null to prevent the Autocomplete going into uncontrolled mode:
<Autocomplete {...props} value={props.value || null} />
If you pass value={undefined} to the Autocomplete component, it will start in "uncontrolled" mode, meaning it keeps its own internal state. Then if you later supply a value it raises the "A component is changing" error. But if you pass value={null}instead of value={undefined} that causes the Autocomplete to start in controlled mode. The Autocomplete will assume you will be providing the state, not keep its own, and the error goes away.
I solved this by removing the default value.
<Autocomplete
multiple
id="multiple-limit-tags"
options={(option) => option.label}
getOptionLabel={(option) => option}
// defaultValue={options || []}
renderInput={(params) => <TextField {...params} label="My Label" />}
/>
It wasn't obvious how to solve this, and the documentation doesn't help much either. I find it curious that a copy-pasted example from the documentation results in this error. I guess the example works because the choices are hard-coded.
Previous answer was absolutely correct, BUT I spend 20 minutes while I figure out that inputValue it should be value
So working example from me:
export default function AddModal(): ReactElement {
const [resource, setResource] = useState('one');
<Autocomplete
id="autocomplete"
options={['one', 'two']}
defaultValue={resource}
value={resource}
PopperComponent={StyledPopper}
onChange={(event, newInputValue) => setResource(newInputValue)}
renderInput={(params) => <TextField {...params} />}
/>
I had the same issue today, but I was able to solve it by providing a default value of null as well as providing a null value in case of it not existing. I'll leave the code that worked for me:
<Autocomplete
value={filters.tag || null}
defaultValue={null}
options={tags || []}
getOptionLabel={(option) => option}
renderInput={(params) => (
<TextField {...params} label="Search" variant='outlined' size='small' />
)}
fullWidth
onChange={(event, value) => {
if (value) {
setFilters({ ...filters, tag: value });
} else {
setFilters({ ...filters, tag: '' });
}
}}
/>
For me I fixed this issue by updated the onChange function and add || null rather then removing the default value since I still need it here is the code
<Box mt={2}>
<Controller
control={control}
name="thematic"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange } }) => (
<Autocomplete
defaultValue={
questionData?.thematic ? questionData?.thematic : null
}
options={thematics}
getOptionLabel={(option) => option.name}
onChange={(event, values) => {
onChange(values || null)
}}
renderInput={(params) => (
<TextField
{...params}
label="Thématique"
placeholder="Thématique"
helperText={errors.thematic?.message}
error={!!errors.thematic}
/>
)}
/>
)}
/>
</Box>
For the autocomplete with options which is a simple array without object you can simply do it that way and u want get any issue
<Box mt={2}>
<Controller
control={control}
name="type"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
<Autocomplete
freeSolo
options={['Champ', 'Sélection', 'Choix multiple']}
onChange={(event, values) => onChange(values)}
value={value}
renderInput={(params) => (
<TextField
{...params}
label="Type de la question"
variant="outlined"
onChange={onChange}
helperText={errors.type?.message}
error={!!errors.type}
/>
)}
/>
)}
/>
</Box>
the default value you can set it within useForm if you are using react-hook-form
const {
handleSubmit,
control,
watch,
register,
formState: { errors },
} = useForm({
defaultValues: {
...
type: questionData?.type ? mapperQuestionType[questionData?.type] : '',
},
})

React Form Hook with Autocomplete Material UI

I have a countries array which contains id and name. Currently I am using a Material UI Autocomplete element and I have a react hook form. When I submit the form, I want to fetch the country Id. Currently it is posting the country name. Is there a way to post the ids instead of the names without going and fetching the id from the name.
<Autocomplete
className="form-item"
options={countries}
getOptionLabel={option => option.name}
renderInput={params => (
<TextField
{...params}
inputRef={register}
label="Country"
name="country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
Use react-hook-form's Controller and put the entire Autocomplete in the as prop. With this, when you submit the form you will get the entire object of the selected option.
Note: In react-hook-form version 6.x, the onChange is removed, the as prop will take a function and you can obtain onChange as param.
Working demo - v6
<Controller
as={({ onChange }) => (
<Autocomplete
className="form-item"
options={countries}
onChange={(_, data) => onChange(data)}
getOptionLabel={option => option.label}
renderInput={params => (
<TextField
{...params}
label="Country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
)}
name="country"
control={control}
defaultValue={{ label: "The Shawshank Redemption", id: "1994" }}
/>
Note: If you are using v5x then see demo and code snippet below.
Working demo - v5
<Controller
as={
<Autocomplete
className="form-item"
options={countries}
getOptionLabel={option => option.label}
renderInput={params => (
<TextField
{...params}
label="Country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
}
name="country"
control={control}
onChange={([, data]) => data}
defaultValue={{ label: "The Shawshank Redemption", id: "1994" }}
/>
Edit: based on comment
You can use setValue to set default values based on an api.
code snippet:
useEffect(() => {
setTimeout(() => { // fake api
setValue(
"country",
{ label: "hi The Godfather", id: "1972" },
{ shouldDirty: true }
);
}, 2000);
}, []);
Demo v6 above is updated.
Also see official demo of setValue usage here
Here is a simplest way to create it, render your Autocomplete component inside Controller from react hook from, use onChange and value from the render function to control the value
<Controller
control={control}
name="type"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
<Autocomplete
freeSolo
options={['field', 'select', 'multiple', 'date']}
onChange={(event, values) => onChange(values)}
value={value}
renderInput={(params) => (
<TextField
{...params}
label="type"
variant="outlined"
onChange={onChange}
/>
)}
/>
)}
I'm afraid that there is not an 'easy' way to get the ids with the current setup.
However, you can hook into the Autocomplete#onChange event and use the value prop to take over the internal value state of the Autocomplete component. This results in having the value available in your component and use this to your advantage.
In the example below, the id will be available in the form data as country-id.
function CountryAutocomplete() {
const [value, setValue] = useState(); // `[]` for multiple
return (
<React.Fragment>
<Autocomplete
className="form-item"
value={value}
onChange={(event, value) => setValue(value)}
options={countries}
getOptionLabel={option => option.name}
renderInput={params => (
<TextField
{...params}
inputRef={register}
label="Country"
name="country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
<input value={value?.id} name="country-id" />
</React.Fragment>
);
}

react- cleaning autocomplete generates an error

I have a form created with material-ui, I have a textfield that uses autocomplete which is filled with a useEffect that is responsible for bringing the data from the API to load the AUTOCOMPLETE.
The data is not saved.
Code
const defaultProps={
options:location.name,
getOptionLabel: option => option.label + "-" + option.phone,
};
<Autocomplete
style={{ width: 300 }}
{...defaultProps}
id="city"
autoComplete
onChange={(event, newVal)=>onChange({target:{name:"name",value: newVal.label }}
)}
renderInput={params => (
<TextField {...params} label="Country" margin="normal" variant="outlined" inputProps={{
...params.inputProps,
}}/>
)}
What is causing that behavior?
It appears your problem in on the following line:
onChange={(event, newVal)=>onTagsChange({target:{name:"label",value: newVal.label }}
It may be related to newValue being null after clearing. You could add a protection such as:
value: newVal || newVal.label
Since it is not clear to me how you are using the resulting selection my suggested protection may not be appropriate.
You may also want to take a look at their "controlled" example in the documentation (https://material-ui.com/components/autocomplete/#playground) in which they use the value attribute to control the AutoComplete:
<Autocomplete
{...defaultProps}
id="controlled-demo"
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
renderInput={params => (
<TextField {...params} label="controlled" margin="normal" fullWidth />
)}
/>

Resources