MaterialUI Autocomplete - Disable option after selection - material-ui

How to disable the selected option in the list of options in autocomplete MUI?
For example, after selecting option "b", it should be disabled.
import Autocomplete from "#material-ui/lab/Autocomplete";
import { TextField } from "#material-ui/core";
function App() {
return (
<Autocomplete
freeSolo
id="free-solo-demo"
options={["a", "b", "c"]}
renderInput={params => (
<TextField
{...params}
label="freeSolo"
margin="normal"
variant="outlined"
fullWidth
/>
)}
/>
);
}

You can achieve this using getOptionDisabled prop. You just have to pass a function to this prop which says to disable the option if already been selected.
There is another prop available in this component called filterSelectedOptions, which filters out the selected option.
You can find both of these working examples over here Stackblitz👈
const [selectedOptions, setSelectedOptions] = useState([]);
const options = ['a', 'b', 'c'];
<Autocomplete
multiple
freeSolo
options={options}
value={selectedOptions}
//disabling selected options
getOptionDisabled={(option) =>
selectedOptions.some((selectedOption) => selectedOption === option)
}
onChange={(_, value) => {
setSelectedOptions(value);
}}
renderInput={(params) => (
<TextField
{...params}
label="Multiple values"
placeholder="Favorites"
/>
)}
/>

Related

mui5 - Autocomplete showing a blank entry when empty

The Autocomplete was working fine in v4. In mui5, the following code causes an empty value selected:
As shown above, an empty value causes a blank entry selected.
<Autocomplete
multiple
id="languagesList"
options={tabPassed.data.languages}
defaultValue={cookies['cookielanguages']?
translateMultiOptions(tabPassed.data.languages, cookies['cookielanguages']):translateMultiOptions(tabPassed.data.languages, currentUser.languages)}
limitTags={3}
groupBy={option => option.frequent}
getOptionLabel={(option) => (option && option.title)? option.title: ""}
onChange={(event, fieldValue) => {handleInputChange(event, fieldValue, "languages")}}
renderInput={(params) => <TextField {...params} label="Language " variant="outlined" placeholder={"Start Typing OR Select"}/>}
/>

Material Ui 5 Autocomplete does not return filtered options

I am using material ui V5,
Due to the default filtering in Autocomplete does not give proper result array, l have written my own filterOptions function.
const filterOpt = (options, state) => {
let result = options.filter(option => option.name.includes(state.inputValue))
return result }
The result returning from the function is exactly what l want. But still, l can see the undesired options.
Here is my Autocomplete component :
<StyledAutocomplete
disabled={disabled}
id="field1"
getOptionLabel={(option) => option.name || ""}
isOptionEqualToValue={(option, value) => option.id === value.id}
value={values[prop] || ""}
noOptionsText={"No options found"}
options={data}
style={{ width: "100%" }}
PopperComponent={PopperMy}
PaperComponent={CustomPaper}
onChange={(event, newValue) =>
setValues({ ...values, [prop]: newValue })
}
filterOptions={(options, state) => filterOpt(options, state)}
renderInput={(params) => {
const inputProps = params.inputProps;
inputProps.autoComplete = "new-password";
return (
<StyledTextField
{...params}
inputProps={{
...params.inputProps,
autoComplete: "new-password",
}}
name="field1"
id="field1"
autoComplete="off"
type="text"
label=""
variant="outlined"
error={error && !values[prop]}
helperText={error && errorStatus ? errorTexts[prop] : ""}
/>
);
}}
/>
Here are the options that l see after filtering
Here is the results array returned from the function:
Is there any solution to show the exact filtered array to users?
This is because of my data array which includes some items with the same key.

material-ui / How to style an HOC using 'withStyles()'?

My HOC :
const withPaper = Component => props => (
<Paper>
<Component {...props} />
</Paper>
);
export default withPaper;
I want to style 'Paper' component using withStyles():
const styles = theme => ({
root: {
backgroundColor: 'green'
}
});
const withPaper = ?? => ?? => (
<Paper className={classes.root}>
<Component {...props} />
</Paper>
);
export default withStyles(styles)(withPaper);
How can I inject classes prop this case?
My simple idea Component => ({classes, ...props}) => logs error.
TypeError: Cannot call a class as a function
Answering my own question.
I ignored the returns of HOC. It returns 'Component' instead of 'React Element'.
I'm not sure, but I think this is the reason that I couldn't inject classes from outside of HOC.
My solution that works well - styling inside of HOC:
const withPaper = Component => {
const WithPaper = ({ classes, ...props }) => (
<Paper className={classes.root}>
<Component {...props} />
</Paper>
);
const styles = theme => ({
root: {
backgroundColor: 'green'
}
});
return withStyles(styles)(WithPaper);
};
export default withPaper;
FYI, TypeScript users can refer to Rahel's answer.
I am in the process of learning Material-UI and TypeScript myself, and I was actually struggling with the very same thing :-) Apologies if you were looking for a JS solution, but the types being explicit might actually help:
import * as React from "react";
import createStyles from "#material-ui/core/styles/createStyles";
import { WithStyles } from "#material-ui/core";
import Paper from "#material-ui/core/Paper/Paper";
import { compose } from "recompose";
import withStyles from "#material-ui/core/styles/withStyles";
const styles = createStyles({
root: {
backgroundColor: "green"
}
});
type WrapperProps = WithStyles<typeof styles>;
const withPaper = <P extends {}>(Component: React.ComponentType<P>) => {
type Props = P & WrapperProps;
return (props: Props) => {
return (
<Paper className={props.classes.root}>
<Component {...props} />
</Paper>
);
};
};
export default compose(withStyles(styles), withPaper);
Note the usage of recompose, to compose your higher-order components. If you mind this library-dependency, you can also do without:
export default (component: React.ComponentType) => withStyles(styles)(withPaper(component));

Autocomplete - How can I set a default value?

Does anyone know how to add a default value to the Autocomplete component?
The component have a dataSource, and I'd like to load the page with a specific item already selected(e.g. fill the text field with the selected item's text and it's value already set).
Does anyone knows how? Big thanks for any help <3
You can achieve this by setting the appropriate state in componentDidMount, for example:
componentDidMount() {
// load your items into your autocomplete
// set your default selected item
this.setState({ allItems: [itemYouWantToSet], selectedItem: item.name, selectedItemId: item.id }
}
render() {
return (
<Autocomplete
value={this.state.selectedItem}
items={this.state.allItems}
getItemValue={(item) => item.name}
onSelect={(value, item) => {
this.setState({ selectedItem: value, selectedItemId: value, allItems: [item] });
}}
/>
)
}
Then your item is correctly selected from the list of available options when it loads.
I tried all the above solutions and nothing worked. Perhaps the API has changed since then.
I finally figured out a solution. It's not so elegant, but in principle it makes sense. In my case the options are objects. I just had to set the "value" prop using the exact item from my options array. This way componentDidMount and getOptionSelected aren't needed.
Autocomplete is wrapped inside another component in our case. This is the main code:
class CTAutoComplete extends React.PureComponent {
getSelectedItem(){
const item = this.props.options.find((opt)=>{
if (opt.value == this.props.selectedValue)
return opt;
})
return item || {};
}
render() {
return (
<Autocomplete
id={this.props.id}
className={this.props.className}
style={{ width: '100%' }}
options={this.props.options}
getOptionLabel={this.props.getOptionLabel}
renderInput={params => (
<TextField {...params} label={this.props.label} variant="outlined" />
)}
onChange={this.props.onChange}
value={this.getSelectedItem()}
/>
);
}
}
IMPORTANT: When setting "value", you have to make sure to put the null case " || {} ", otherwise React complains you are changing from an uncontrolled to controlled component.
you can provide the defaultValue prop for AutoComplete.
<Autocomplete
multiple
id="tags-outlined"
options={this.state.categories}
getOptionLabel={(option) => option.category_name}
onChange={this.handleAutocomplete}
defaultValue={'yourDefaultStringValue'} //put your default value here. It should be an object of the categories array.
filterSelectedOptions
renderInput={(params) => (
<TextField
fullWidth
{...params}
variant="outlined"
label="Add Categories"
placeholder="Category"
required
/>
}
/>
This approach works for me (using hooks):
First of all define the options you need in a variable:
const genderOptions = [{ label: 'M' }, { label: 'V' }];
Second you can define a hook to store the selected value (for example store it in session storage for when the page refreshes, or use useState directly):
const age = useSessionStorage('age', '');
Next you can define your Autocomplete as follows (notice the default values in value and getOptionLabel, if you omit those you'll get those controlled to uncontrolled warnings):
<Autocomplete
id="id"
options={ageOptions}
getOptionLabel={option => option.label || ""}
value={ageOptions.find(v => v.label === age[0]) || {}} // since we have objects in our options array, this needs to be a object as well
onInputChange={(_, v) => age[1](v)}
renderInput={params => (
<TextField {...params} label="Leeftijd" variant="outlined" />
)}
/>
It is tricky specially in case of you are using along with filter option which load API on every filter. I was able to load initial value by setting up within state and onInputChange option.
Below is code that work for me or click below link for full working demo.
https://codesandbox.io/s/smoosh-brook-xgpkq?fontsize=14&hidenavigation=1&theme=dark
import React, { useState, useEffect } from "react";
import TextField from "#material-ui/core/TextField";
import Typography from "#material-ui/core/Typography";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function CreateEditStrategy({ match }) {
const [user, setUser] = useState({
_id: "32778",
name: "Magic User's Club!"
});
const [filter, setFilter] = useState("");
const [users, setUsers] = useState([]);
const [openAutoComplete, setOpenAutoComplete] = React.useState(false);
useEffect(() => {
(async () => {
//Will not filter anything for testing purpose
const response = await fetch(
`https://api.tvmaze.com/search/shows?q=${filter}`
);
const shows = await response.json();
setUsers(
shows.map((a, i) => {
return { _id: a.show.id, name: a.show.name };
})
);
})();
}, [filter]);
return (
<div>
<Typography variant="h6">Autocomplete</Typography>
<Autocomplete
open={openAutoComplete}
onOpen={() => setOpenAutoComplete(true)}
value={user}
inputValue={filter}
onClose={() => setOpenAutoComplete(false)}
onChange={(event, user) => {
if (user) setUser(user);
else setUser({ _id: "", name: "" });
}}
onInputChange={(event, newInputValue) => setFilter(newInputValue)}
getOptionSelected={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={users}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
InputProps={{
...params.InputProps
}}
/>
)}
/>
</div>
);
}
Call your component like this
<SelectCountryAutosuggest searchText="My Default Value" />
Make sure you apply the default value to state on class load
class SelectCountryAutosuggest extends React.Component {
state = {
value: this.props.searchText, //apply default value instead of ''
suggestions: [],
};
...
}
The api docs suggest the best approach in the current version (June 2022) is to use value and isOptionEqualToValue.
So for example, if I have a list of users and am choosing which user this thing is assigned to:
const [assignedTo, setAssignedTo] = useState(initialOption);
return (<Autocomplete
options={users.map((i) => ({
label: i.name,
value: i._id,
}))}
isOptionEqualToValue={(o, v) => o.value === v.id}
value={assignedTo}
onChange={(evt, val) => setAssignedTo(val)}
renderInput={(params) => (
<TextField {...params} label="Assigned To" />
)}
/>);
We can setup initial value through value property of Autocomplete component
<Autocomplete
fullWidth={true}
label={'Location'}
margin={'noraml'}
multiple={false}
name={'location'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
value={formValues.location === '' ? {label: ''} : {label: formValues.location}}
options={location}
ref={locationRef}
onChange={useCallback((e, v) => handleInputChange(e, v))}
/>
You can use the searchText property.
<AutoComplete searchText="example" ... />
Try this...
componentWillReceiveProps(nextProps) {
let value = nextProps.value
if (value) {
this.setState({
value: value
})
}
}
onUpdateInput worked for me - for anyone looking through all this as I was
Have you tried setting the searchText prop dynamically? You can pass the value you want to set to the Autocomplete component as the searchText prop. Something like,
<Autocomplete
searchText={this.state.input.name} // usually value={this.state.input.name}
/>
By default, it'll have the initial value set into the TextField of the Autocomplete component but when the user makes any modifications, it calls up the Autocomplete options depending on the dataSource prop.

How to set focus to a materialUI TextField?

How can I set the focus on a material-ui TextField component?
componentDidMount() {
ReactDom.findDomNode(this.refs.myControl).focus()
}
I have tried above code, but it does not work :(
For React 16.8.6, you should use the inputRef property of TextField to set focus. I tried ref property but it doesn't work.
<TextField
inputRef={input => input && input.focus()}
/>
Material-ui doc says:
inputRef: Use this property to pass a ref callback to the native input component.
You can use the autoFocus attribute.
<TextField value="some value" autoFocus />
autoFocus was also not working for me, perhaps since this is a component that's not mounted when the top-level component loads. I had to do something a lot more convoluted to get it to work:
function AutoFocusTextField(props) {
const inputRef = React.useRef();
React.useEffect(() => {
const timeout = setTimeout(() => {
inputRef.current.focus();
}, 100);
return () => {
clearTimeout(timeout);
};
}, []);
return <TextField inputRef={inputRef} {...props} />;
}
Note that for some reason it does not work without the setTimeout. For more info see https://github.com/callemall/material-ui/issues/1594.
If you are using material-ui TextField and react functional component, you can pass inputRef in your TextField component. The trick here is the if condition if(input != null).
<TextField
variant="filled"
inputRef={(input) => {
if(input != null) {
input.focus();
}
}}
/>
Here is an working example for you. CodeSandBox- Material-ui-TextFieldFocus
add this propery to your TextField component :
inputRef={(input) => input?.focus()}
This will focus the component every time it renders. Other solutions I tried only focus the element an initial time.
const inputRef = React.useRef<HTMLInputElement>();
useEffect(() => {
inputRef.current?.focus();
}, [inputRef.current]);
const setTextInputRef = (element: HTMLInputElement) => {
inputRef.current = element;
};
return (
<TextField
inputRef={setTextInputRef}
/>
const handleClick = () => {
inputRef.current.firstChild.focus();
inputRef.current.firstChild.placeholder = '';
}
<InputBase
value={value}
ref={inputRef}
placeholder="search" />
<Button onClick={handleClick}>Click</Button>
This code is actually good, but has a drawback, on every render it's going to create a new function. It easily can be solved using useCallback
<TextField
inputRef={input => input && input.focus()}
/>
Should be
const callbackRef = useCallback((inputElement) => {
if (inputElement) {
inputElement.focus();
}
}, []);
...
<TextField
inputRef={callbackRef}
/>
useRef hook simple example:
const focusMe_Ref = useRef(null); // 1. create
useEffect(() => {
focusMe_Ref.current.focus(); // 2. startup
}, []);
...
<TextField
inputRef={focusMe_Ref} // 3. will focused
...
/>
I am using this solution, works for text fields inspired by https://gist.github.com/carpben/de968e377cbac0ffbdefe1ab56237573
const useFocus = (): [any, () => void] => {
const htmlElRef: MutableRefObject<any> = useRef<HTMLDivElement>();
const setFocus = (): void => {
if (!htmlElRef || !htmlElRef.current) return
const div = htmlElRef.current as HTMLDivElement
if (!div) return
const input = div.querySelector("input")
if (input) input.focus()
}
return [htmlElRef, setFocus];
};
export function MyComp() {
const [ref, setFocus] = useFocus()
// use setFocus() to focus the input field
return <Input ref={ref} />
}
I had a similar problem where the input field didn't regain focus after I've modified its contents with external controls (an emoji picker).
I ended up with this brute workaround hook:
const useEnforceFocusTextField = (textFieldRef: React.RefObject<HTMLInputElement>) => {
const [enforcedRerender, setEnforcedRerender] = useState(false);
React.useEffect(() => {
textFieldRef.current?.focus();
const timeout = setTimeout(() => {
textFieldRef.current?.focus();
}, 100);
return () => clearTimeout(timeout);
}, [enforcedRerender]);
return () => setEnforcedRerender((n) => !n);
};
From the outside, you call utilize this hook in the following manner:
const textFieldRef = useRef<HTMLInputElement>(null);
...
// enforceFocus() can be called in any callback to set the focus to the textfield
const enforceFocus = useEnforceFocusTextField(textFieldRef);
...
return <TextField inputRef={textFieldRef} ... />
For a material ui TextField you need to input the props for autoFocus in a inputProps object like this.
<TextField inputProps={{ autoFocus: true }} />
AlienKevin is correct ( pass a ref callback to "TextField.inputProps" ), but you can also save the element reference on your "this" object, so that you can set focus later. Here is an example in Coffeescript:
TextField
inputProps:
ref: (el)=>
if el?
#input_element = el
Button
onClick:=>
#input_element.focus()