I have an Autocomplete field which uses:
searchText = {this.state.searchText}
like this;
<AutoComplete
floatingLabelText='agent input'
ref='agentInput'
hintText="type response"
multiLine = {true}
fullWidth = {true}
searchText = {this.state.searchText}
onNewRequest={this.sendAgentInput}
dataSource={this.agentCommands}
/>
But when I update the this.setState({searchText: null })
it will clear the autoComplete once, but not the second time.
Not sure if this is a bug or if there's another way to reset the field.
I also tried looking for the field and adding a ref but no luck.
Filed here in case it's a bug
https://github.com/callemall/material-ui/issues/2615
Try also to change searchText on every input update:
onUpdateInput={this.handleUpdateInput}
This function should change searchText whenever user changes the input:
handleUpdateInput(text) {
this.setState({
searchText: text
})
}
My code looks as follows (ES6):
class MyComponent extends Component {
constructor (props) {
super(props)
this.dataSource = ['a', 'b' ,'c']
this.state = {
searchText: ''
}
}
handleUpdateInput (t) { this.setState({ searchText: t }) }
handleSelect (t) { this.setState( { searchText: '' }) }
render () {
return <AutoComplete dataSource={this.dataSource}
searchText={this.state.searchText}
onNewRequest={this.handleSelect.bind(this)}
onUpdateInput={this.handleUpdateInput.bind(this)}
/>
}
}
Here I want to clear input when user presses enter or chooses some item from the list (so I clear searchText in handleSelect) but I also change state of searchText on every input update (handleUpdateInput).
I hope it will work for you!
Try this :
this.setState({ searchText: "\r" });
because I think the function should test the length of the string (BUG ?)
For freesolo Autocomplete, you can control the inputValue of Autocomplete and set it to an empty string on demand:
const [inputValue, setInputValue] = React.useState('');
return (
<>
<Button onClick={() => setInputValue('')}>clear input</Button>
<Autocomplete
freeSolo
inputValue={inputValue}
onInputChange={(_, v) => setInputValue(v)}
options={top100Films}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
</>
);
If you also want to change the currently selected value, not just the value in the input box:
const [inputValue, setInputValue] = React.useState('');
const [value, setValue] = React.useState(null);
return (
<>
<Button
onClick={() => {
setInputValue('');
setValue(null);
}}
>
clear input
</Button>
<Autocomplete
freeSolo
value={value}
onChange={(_, v) => setValue(v)}
inputValue={inputValue}
onInputChange={(_, v) => setInputValue(v)}
options={top100Films}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
</>
);
Related
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",
},
...
]
I am using Material UI v4.
I have a component which takes all the users and show them in dropdown but I am not able to select default value. I tried following sandbox but it did not worked for me: [https://codesandbox.io/s/material-demo-4mhcj?file=/demo.js][CodeSandBox]
For ex- test#gmail.com
react-hook-form : ^7.20.5
I tried giving a default value to both Controller and Autocomplete but it does not work for the first time.
Any idea what I am doing wrong here?
// data is something like
// [{name: 'ABCDE', email: 'ABCDEEE#gmail.com'}, {name: 'eeeeee', email: 'eeeeee12#gmail.com'}]
const UserSelection = ({ data = [], onChangeHandler }) => {
const { control } = useForm({});
const selectedUserEmail = "ABCDEEE#gmail.com";
return (
<>
<Controller
name="combo-box-demo"
control={control}
defaultValue={data.find(item => item.email === selectedUserEmail)}
render={() =>
<Autocomplete
id="combo-box-demo"
size="small"
options={data}
defaultValue={data.find(item => item.email === selectedUserEmail)}
onChange={onChangeHandler}
getOptionLabel={option => option.name }
renderOption={option => option.name}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
placeholder="Select"
/>
)}
/>
}
/>
</>
);
};
export default UserSelection;
I found the answer, so I am posting here:
The problem was data which was coming is asynchronous and autocomplete was not able to render it properly.
For now I am using:
const UserSelection = ({ data = [], onChangeHandler }) => {
const selectedUser = {name: 'ABCDE', email: 'ABCDEEE#gmail.com'};
return (
<Autocomplete
id="combo-box-demo"
options={data}
defaultValue={selectedUser}
onInputChange={onChangeHandler}
getOptionLabel={option => option.name }
renderOption={option => option.name}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
placeholder="Select"
/>
)}
/>
);
};
export default UserSelection;
And where ever I am using above component, I use like
data ? <UserSelection/> : null
Also instead of using onChange I used onInputChange which helps in getting default value
then it works and loads default user, hope it helps to community
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
I have a form with a field that needs to show suggestions via an api call. The user should be allowed to select one of those options or not and that value that they type in gets used to submit with the form, but this field is required. I am using Formik to handle the form, Yup for form validation to check if this field is required, downshift for the autocomplete, and Material-UI for the field.
The issue comes in when a user decides not to use one of the suggested options and the onBlur triggers. The onBlur always resets the field and I believe this is Downshift causing this behavior but the solutions to this problem suggest controlling the state of Downshift and when I try that it doesn't work well with Formik and Yup and there are some issues that I can't really understand since these components control the inputValue of this field.
Heres what I have so far:
const AddBuildingForm = props => {
const [suggestions, setSuggestions] = useState([]);
const { values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
modalLoading,
setFieldValue,
setFieldTouched,
classes } = props;
const loadOptions = (inputValue) => {
if(inputValue && inputValue.length >= 3 && inputValue.trim() !== "")
{
console.log('send api request', inputValue)
LocationsAPI.autoComplete(inputValue).then(response => {
let options = response.data.map(erlTO => {
return {
label: erlTO.address.normalizedAddress,
value: erlTO.address.normalizedAddress
}
});
setSuggestions(options);
});
}
setFieldValue('address', inputValue); // update formik address value
}
const handleSelectChange = (selectedItem) => {
setFieldValue('address', selectedItem.value); // update formik address value
}
const handleOnBlur = (e) => {
e.preventDefault();
setFieldValue('address', e.target.value);
setFieldTouched('address', true, true);
}
const handleStateChange = changes => {
if (changes.hasOwnProperty('selectedItem')) {
setFieldValue('address', changes.selectedItem)
} else if (changes.hasOwnProperty('inputValue')) {
setFieldValue('address', changes.inputValue);
}
}
return (
<form onSubmit={handleSubmit} autoComplete="off">
{modalLoading && <LinearProgress/>}
<TextField
id="name"
label="*Name"
margin="normal"
name="name"
type="name"
onChange={handleChange}
value={values.name}
onBlur={handleBlur}
disabled={modalLoading}
fullWidth={true}
error={touched.name && Boolean(errors.name)}
helperText={touched.name ? errors.name : ""}/>
<br/>
<Downshift id="address-autocomplete"
onInputValueChange={loadOptions}
onChange={handleSelectChange}
itemToString={item => item ? item.value : '' }
onStateChange={handleStateChange}
>
{({
getInputProps,
getItemProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
}) => (
<div>
<TextField
id="address"
label="*Address"
name="address"
type="address"
className={classes.autoCompleteOptions}
{...getInputProps( {onBlur: handleOnBlur})}
disabled={modalLoading}
error={touched.address && Boolean(errors.address)}
helperText={touched.address ? errors.address : ""}/>
<div {...getMenuProps()}>
{isOpen ? (
<Paper className={classes.paper} square>
{suggestions.map((suggestion, index) =>
<MenuItem {...getItemProps({item:suggestion, index, key:suggestion.label})} component="div" >
{suggestion.value}
</MenuItem>
)}
</Paper>
) : null}
</div>
</div>
)}
</Downshift>
<Grid container direction="column" justify="center" alignItems="center">
<Button id="saveBtn"
type="submit"
disabled={modalLoading}
className = {classes.btn}
color="primary"
variant="contained">Save</Button>
</Grid>
</form>
);
}
const AddBuildingModal = props => {
const { modalLoading, classes, autoComplete, autoCompleteOptions } = props;
return(
<Formik
initialValues={{
name: '',
address: '',
}}
validationSchema={validationSchema}
onSubmit = {
(values) => {
values.parentId = props.companyId;
props.submitAddBuildingForm(values);
}
}
render={formikProps => <AddBuildingForm
autoCompleteOptions={autoCompleteOptions}
autoComplete={autoComplete}
classes={classes}
modalLoading={modalLoading}
{...formikProps} />}
/>
);
}
Got it to work. Needed to use handleOuterClick and set the Downshift state with the Formik value:
const handleOuterClick = (state) => {
// Set downshift state to the updated formik value from handleOnBlur
state.setState({
inputValue: values.address
})
}
Now the value stays in the input field whenever I click out.
I have a selectField and I want to set a value on it. Let say I type on it and when I click a button, the button will call a function that will reset the value of the textfield?
<TextField hintText="Enter Name" floatingLabelText="Client Name" autoWidth={1} ref='name'/>
You can do it in this way
export default class MyCustomeField extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Enter text',
};
}
handleChange = (event) => {
this.setState({
value: event.target.value,
});
};
handleClick = () => {
this.setState({
value:'',
});
};
render() {
return (
<div>
<TextField
value={this.state.value}
onChange={this.handleChange}
/>
<button onClick={this.handleClick}>Reset Text</button>
</div>
);
}
}
It's maintained that the right way is to have the component be controlled in a scenario like the accepted answer there, but you can also control the value in this gross and culturally unacceptable way.
<TextField ref='name'/>
this.refs.name.getInputNode().value = 'some value, hooray'
and you can of course retrieve the value like so
this.refs.name.getValue()
Instead of using ref you should use inputRef
const MyComponent = () => {
let input;
return (
<form
onSubmit={e => {
e.preventDefault();
console.log(input.value);
}}>
<TextField
hintText="Enter Name"
floatingLabelText="Client Name"
autoWidth={1}
inputRef={node => {
input = node;
}}/>
</form>
)
};
Here is a short simple React Hook version of the answer
export default function MyCustomeField({
initialValue= '',
placeholder= 'Enter your text...'
}) {
const [value, setValue] = React.useState(initialValue)
return (
<div>
<TextField
placeholder={placeholder}
value={value}
onChange={e => setValue(e.target.value)}
/>
<button onClick={() => setValue(initialValue)}>Reset Text</button>
</div>
);
}
I would suggest to do a little wrap on the original MUI TextField.
export default function ValueTextField(props) {
const [value, setValue] = useState(props.value);
return (
<TextField {...props} onChange={(event) => setValue(event.target.value)} value={value}/>
);
}
Now you can use your own ValueTextField component now.
<ValueTextField value={"hello world"}></ValueTextField>
I prefer this way to assign the state variables:
<TextField value={mobileNumber}
onChange={e => { this.setState({ mobileNumber: e.target.value }) }}
className={classes.root}
fullWidth={true}
label={t('mobile number')} />