ReactJS Material UI how to prevent Autocomplete to change the value - reactjs

I have an Autocomplete component and I need to prevent it for selecting some kind of values.
const options = ["Option 1", "Option 2", "Option 3"];
const [value, setValue] = React.useState(options[0]);
const [inputValue, setInputValue] = React.useState("");
const change = (event, newValue) => {
event.preventDefault();
event.stopPropagation();
if (newValue !== "Option 3") {
setValue(newValue);
}
};
console.log(value);
return (
<div>
<Autocomplete
value={value}
onChange={change}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
disableClearable
options={options}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
</div>
);
Currently the condition does not change the state, but it does change the value on the component. How can I prevent this to happen?
Here is the sandbox.

You are setting value again here, dont update on this event
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
set inputValue={value}
Updated here,
https://codesandbox.io/s/material-demo-forked-jmr6e?file=/demo.js

Related

value returns 0 or undefined on MUI autocomplete unable to fetch the value in multiple attribute

On MUI Autocomplete component where the attribute is multiple, then the value prop returns 0 or undefined when we choose anyone of the selected option.
<Autocomplete
value={v.education}
onChange={handleEducationChange}
className={classes.textOutline}
multiple
id="virtualize-demo"
name="education"
style={{ width: 600 }}
disableCloseOnSelect
ListboxComponent={ListboxComponent}
options={educationList()}
getOptionLabel={(option) => option.title}
isOptionEqualToValue={(option, value) =>
option.title === value.title
}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.title}
</li>
)}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
name="education"
placeholder="Select your Qualification"
label="Education"
fullWidth
/>
)}
/>
// handleEducationChange
const initialState = { education: "", occupation: "" };
const [selectedOption, setSelectedOption] = useState([], initialState);
const handleEducationChange = (event) => {
const { name, values } = event.target;
setSelectedOption({ ...selectedOption, [name]: values });
console.log(values);
};
I need the selected value to be passed with value prop and that can be validated and posted to backend... kindly update the solution for this, Thanks.

MUI Autocomplete (multiple) controlled values - mysterious input behavior

I am trying to write code to asynchronously search a multiple-select combo upon keyboard entry.
However I found in latest version (5.2.2) a strange behaviour where I cannot explain. I distill the issue below (based on example from MUI's autocomplete page):
import * as React from "react";
import TextField from "#mui/material/TextField";
import Autocomplete from "#mui/material/Autocomplete";
const options = [
{ label: "Option 1", value: 1 },
{ label: "Option 2", value: 2 }
];
export default function ControllableStates() {
// const [value, setValue] = React.useState<any | null>([]);
const value = [];
const [inputValue, setInputValue] = React.useState("");
console.log("Current Value:", value);
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
multiple={true}
value={value}
onChange={(event: any, newValue: any | null) => {
//setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" />}
/>
</div>
);
}
The codeSandbox is as follows: https://codesandbox.io/s/controllablestates-material-demo-forked-ygqp2?file=/demo.tsx
If you try in the codeSandbox, you will be unable to type anything in the TextField field.
However, if you switch the commenting:
const [value, setValue] = React.useState<any | null>([]);
// const value = [];
You will be able to type in the TextField field. What is actually happening here? The value did not change at all.
Can anyone figure out why my first code (where the value is a const empty array) didn't work?
The reason I am asking is that I need to pass in the (controlled) value as props, and then set it to default to [] if it is null. I find that I am unable to type in the TextField due to this defaulting.
First, you could use the Autocomplete component without inputValue and OnInputValue props.
...
<Autocomplete
multiple
value={value}
onChange={(event: any, newValue: any | null) => {
//setValue(newValue);
}}
id="controllable-states-demo"
options={options}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" />}
/>
But it won't work for the selection, only search will work.
Second, if you want its search as well as selection to work, then you should use more a couple of Autocomplete props.
...
export default function ControllableStates() {
const [value, setValue] = React.useState<any | null>([]);
// you need to set the selected value your own
// const value = [];
const [inputValue, setInputValue] = React.useState("");
console.log("Current Value:", value);
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
multiple
value={value}
onChange={(event: any, newValue: any | null) => {
setValue(newValue.map(option => option.value || option));
}}
isOptionEqualToValue={(option, value) => option.value === value}
getOptionLabel={(option) => {
if (typeof option === 'number') {
return options.find(item => item.value === option)?.label;
} else {
return option.label;
}
}}
id="controllable-states-demo"
options={options}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" />}
/>
</div>
);
}
As you can see it doesn't need to use the inputValue and onInputChange props as well.
Please make sure if you match the correct types of the selected value and option.
If you are using react-hook-form you can set up the autocomplete by using
multiple to add multiple values,
options: you add the options to be selected
getOptionLabel: to show up the label of the options
onChange: use onChange function of react-hook-form to set the selected values
renderInput: to render the input
import { useForm, Controller } from 'react-hook-form'
import {
Box,
TextField,
Autocomplete,
} from '#mui/material'
const {
...
control,
formState: { errors },
} = useForm()
<Box mt={2}>
<Controller
control={control}
name="industries"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange } }) => (
<Autocomplete
defaultValue={
useCasesData?.industries
? JSON.parse(useCasesData?.industries)
: []
}
multiple
disableCloseOnSelect
options={companyIndustryTypes}
getOptionLabel={(option) => option.name}
onChange={(event, values) => {
onChange(values)
}}
renderInput={(params) => (
<TextField
{...params}
label="Type d'industries"
placeholder="Type d'industries"
helperText={errors.industries?.message}
error={!!errors.industries}
/>
)}
/>
)}
/>
</Box>
Note that options in my case companyIndustryTypes is an array of object :
[
{
id: 1,
name: "Accounting",
},
{
id: 2,
name: "Administration & Office Support",
},
...
]

Showing a value (instead of label) on a Material UI Autocomplete

So I have an async combo box that pulls options from an API. The options are just an Id and a description. This component is part of a form that I'm showing to add or edit data. What I'd like to see happen is have the option be empty when adding new data and to have the current value selected when editing. Instead, it simply shows the label.
This is my code that's almost a copypaste of the example from the docs.
export default function AsyncAutoComplete(props:AsyncAutoCompleteProps) {
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState<EntityWithIdAndDescription[]>([]);
const loading = open && options.length === 0;
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
props.populateWith().then((options)=> {
if (active) {
setOptions(options);
}})
})();
return () => {
active = false;
};
}, [loading]);
React.useEffect(() => {
if (!open) {
setOptions([]);
}
}, [open]);
return (
<Autocomplete
id="async-autocomplete"
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
onChange={props.onChange}
getOptionSelected={(option, value) => option.id === value.id}
getOptionLabel={(option) => option.description}
options={options}
loading={loading}
renderInput={(params) => (
<TextField
{...params}
label={props.label}
variant="outlined"
margin="dense"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
What I want is to pass an Id value to this component and have it show the description for that value as the selected option (after making the API call). Using defaultValue doesn't seem to work.
Any advice with either modifying this or taking a different approach would be helpful.
Looks like what you're after is a controlled component. There's an example of this in Material UI's Autocomplete demo under Playground called controlled.
I'm not sure how you're getting the id of the initial value you want to pass to the component to show that it's selected. It could be something like the following.
Create a separate state for the value you select from this Autocomplete in your parent component. In fact, I would not have a separate component called AsyncAutocomplete at all. This is so you control all your state in the parent component and the Autocomplete component becomes purely presentational.
After your API call is complete and the setOptions(options) is called, call setValue with the value that you would like to show selected. This must be of type EntityWithIdAndDescription.
Create an inline-function for the onChange prop of the Autocomplete component which takes a the second parameter as the EntityWithIdAndDescription | null type. This is what's required from Autocomplete's onChange. Call setValue with this parameter as the argument.
Pass options, value, onChange and loading as props into the Autocomplete component. The additional props I've passed over and above what you've done in your code are:
<Autocomplete
...
disabled={loading}
value={value}
...
/>
Let me know how you go
const [value, setValue] = useState<EntityWithIdAndDescription | null>(null); // (1)
const [options, setOptions] = useState<EntityWithIdAndDescription[]>([]);
const loading = options.length === 0;
useEffect(() => {
populateWith().then((options)=> {
setOptions(options);
})
// (2)
setValue({
id: "something",
description: "something",
})
return () => {};
}, []);
// (4)
<Autocomplete
id="async-autocomplete"
disabled={loading}
onChange={(event: any, newValue: EntityWithIdAndDescription | null) => {
setValue(newValue); // (3)
}}
getOptionSelected={(option, value) => option.id === value.id}
getOptionLabel={(option) => option.description}
options={options}
loading={loading}
value={value}
renderInput={(params) => (
<TextField
{...params}
label={"My Entities"}
variant="outlined"
margin="dense"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
Code Sandbox
Here's an example with the Autocomplete in a separate component I called MyAutocomplete. It includes an API call and setting a value I want to be selected first.
https://codesandbox.io/s/autumn-silence-lkjrf

Material UI Autocomplete component issue working with check

I have a Material UI autocomplete component with a checkbox component. How can I get both of them working such that only when a user selects an option from the autocomplete, the checkbox should get checked. Here is the link to my component:
https://codesandbox.io/embed/material-demo-forked-of2cz?codemirror=1
https://codesandbox.io/s/material-demo-forked-of2cz?from-embed=&file=/demo.tsx
i finally got my autocomplete/checkbox to work and be all checked by default (redux & ts)
const HIDDEN_USERS: HiddenUsersProps[] = store.getState().user.hiddenUsers
// array of objects
//{
// uid:"oKW8wDsasYdssdd1wTLggsas02"
// userName:"Matt"
//}
const [value, setValue] = useState<HiddenUsersProps[]>([])
//the only way i found to wait for the values from the redux store
useEffect(() => {
setValue(HIDDEN_USERS)
}, [HIDDEN_USERS])
const handleChange = (newValue: HiddenUsersProps[]) => {
setValue(newValue)
}
return (
<Autocomplete
options={HIDDEN_USERS}
value={value}
onChange={(_, newValue: HiddenUsersProps[]) => handleChange(newValue)}
getOptionLabel={(option: any) => option.userName}
isOptionEqualToValue={(option, value) => option.userName === value.userName}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox icon={icon}
checkedIcon={checkedIcon}
checked={selected} />
{option.userName}
</li>
)}
renderInput={(params) => (
<TextField {...params}
label="Hidden Users"
placeholder="Name"
variant="outlined" />)} />
)
"#mui/material": "^5.0.6",
You should change your component like this :
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import Checkbox from "#material-ui/core/Checkbox";
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState<string | null>("");
const [inputValue, setInputValue] = React.useState("");
const [checked, setChecked] = React.useState<boolean>(false);
const [text1, setText1] = React.useState("");
const [text2, setText2] = React.useState("");
const isTextFieldsNotEmpty = text1.length > 0;
const handleFirstTextChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setChecked(!checked)
setText1(event.target.value);
};
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}' '${checked}' '${text1}'`}</div>
<br />
<Checkbox
checked={checked}
onChange={handleFirstTextChange}
inputProps={{ "aria-label": "primary checkbox" }}
/>
<Autocomplete
value={value}
onChange={(event: any, newValue: string | null) => {
setValue(newValue);
setChecked(!checked)
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={(params) => (
<TextField
{...params}
onChange={handleFirstTextChange}
label="Controllable"
variant="outlined"
/>
)}
/>
</div>
);
}
I just have put the setChecked inside the same event listener than your textfield.
That would create the behaviors you search when the user select a field is checking the checkbox.

How can I clear autocomplete values

I am working with material ui autocomplete.
<Autocomplete
multiple
id="tags-outlined"
options={options}
getOptionLabel={(option) => option && option.name}
value={catArray}
onChange={handleCategoryFilter}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
placeholder="Categories"
/>
)}
/>
Firstly I want selected value not to show in options secondly I want to clear autocomplete. Can you please guide me how can I do these things.
I am clearing the values by setCatArray([]) but it is not workinh
You can use material ui autocomplete's default prop filterSelectedOptions. If true, it will hide the selected options from the list box.
For clearing the values of autocomplete, it gives default clear icon at the end of the select box. you can clear it from there.
Try my sandbox link. Also try some more material ui demos here.
UPDATE: If you want to manage clearing values you can use value prop of autocomplete and manage it in onchange by updating it's value.
<Autocomplete
multiple
id="tags-outlined"
options={top100Films}
value={values}
getOptionSelected={(option, value) => value.title === option.title}
getOptionLabel={(option) => option.title}
filterSelectedOptions
onChange={(e, valueTags) => {
e.preventDefault();
const valuesArray = [];
valueTags.forEach((valueTag) => {
valuesArray.push({
title: top100Films
.filter((tag) => valueTag.title === tag.title)
.shift().title
});
});
setValues(valuesArray);
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
Have you tried adding autocomplete=false to the list of properties in the component?
If you want to filter out selected options from autocomplete. Pass filterSelectedOptions={true} to your component
Secondly, you can clear the value by taking control of the selected values and setting it to an empty array. Here is my complete example
const options = ["one", "two", "three", "four"];
export default function MyAutocomplete() {
const [values, setValues] = React.useState<string[]>([]);
const onChange = (_, value) => {
setValues(value);
};
const clearSelected = () => {
setValues([]);
};
return (
<>
<button onClick={clearSelected}>Clear selected</button>
<Autocomplete
multiple
id="tags-outlined"
options={options}
getOptionLabel={(option) => option}
value={values}
onChange={onChange}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} variant="outlined" placeholder="Categories" />
)}
/>
</>
);
}
Live Example
const options = ["one", "two", "three", "four"];
export default function MyAutocomplete() {
const [values, setValues] = React.useState([]);
const onChange = (_, value) => {
setValues(value);
};
const clearSelected = () => {
setValues([]);
};
return (
<>
<button onClick={clearSelected}>Clear selected</button>
<Autocomplete
multiple
id="tags-outlined"
options={options}
getOptionLabel={(option) => option}
value={values}
onChange={onChange}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} variant="outlined" placeholder="Categories" />
)}
/>
</>
);
}

Resources