How to prevent MUI Autocomplete from closing when its input looses focus - javascript

I have a MUI autocomplete with a a list of items and an edit icon next to each one which rerenders the option as a textfield to allow changing the name of the option but when I click inside textfield the dropdonwn closes.
I tried using autoFocus on the textfield but that makes the dropdown close as soon as I click the edit icon
It seems that whenever the autocomplete input looses focus it closes, is there any way to prevent this?
const [isEdit, SetIsEdit] = useState(false);
const [name, SetName] = useState('Blue');
<Autocomplete
options={data}
value={val}
onChange={(e, val)=> {handleChange(e, val)}}
filterSelectedOptions
// --------------- OPTION RENDER ---------------
renderOption={(props, option) => {
isEdit ?
<Box {...props}>
<Textfield value={name} onChange={()=>{setName(e.target.value)}}/>
<DoneIcon onClick={()=>{setIsEdit(false)}}/>
</Box>
:
<Box {...props}>
<Typography>{option.label}</Typography>
<EditIcon onClick={()=>{setIsEdit(true)}}/>
</Box>
}}
/>
);
}

You could be looking for the Autocomplete disableCloseOnSelect boolean prop. The MUI docs say this about the prop:
If true, the popup won't close when a value is selected.
The popup is the box that appears containing the autocomplete drop-down list of options.

Related

Material UI autocomplete closes on click when it was supposed to display a popover

I am trying to add a button to the Material UI autocomplete throgh the renderOption prop and added a button at each row which was supposed to display a PopOver component, but clicking on the button automatically closes the autocomplete search results
How can I prevent the autocomplete search results from closing on click
Here is a sandbox with my code: https://codesandbox.io/s/compassionate-rgb-z88y10
I have already checked this other similar issue, but in my case I am trying to set thr renderOption prop and not the PaperComponent
But unfortunatelly in my case it wasn´t enough to just set the function to run on the onMouseDown event handler
You can create a controlled Autocomplete by using the open prop on the component. The onClose gives you various reasons why the Popover wants to close, you can find them all in the MUI docs.
We check if the reason is selectOption if it is we do nothing and return out. If it is not we set isOpen to false to close the Popover
const [isOpen, setIsOpen] = useState(false);
return (
<Autocomplete
renderOption={(props, label) => (
<PopOver
{...props}
id={props.id}
label={label.label}
message={label.label}
/>
)}
id="combo-box-demo"
open={isOpen}
onOpen={() => setIsOpen(true)}
onClose={(e, reason) => {
if (reason === "selectOption") return;
setIsOpen(false);
}}
options={top100Films}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
);

MUI Autocomplete with FreeSolo not detecting value of onInputChange

I'm using MUI's Autocomplete component with freeSolo so that the user may either select an option from the dropdown or input a value into the textfield. I am able to obtain the value of both: when the user selects and types in an arbitrary value.
However, it's only detecting the value onChange, not onInputChange. I need to distinguish between the value being selected from the options or entered as free text. I need to be able to prompt the user that they are submitting a unique option.
This is the basic MUI Autocomplete component:
export default function ControllableStates() {
const [value, setValue] = React.useState(null);
const [inputValue, setInputValue] = React.useState('');
return (
<div>
<div>{`InputValue: '${inputValue}'`}</div>
<div>{`Value: '${value}'`}</div>
<br />
<Autocomplete
freeSolo
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={data.map((option) => option.name)}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" />}
/>
</div>
);
}

touched property not being reset

I have a Formik form where I take user input input & run a query after submitting.
When I click on the search button, a list is rendered. All list items have a button of their own. When I click on the green add button (from the list items) for the first time, the button does not work. Console log's content is not printed. Instead, the onBlur event of the inputField is triggered. However, if I click on the + button again, then it works and prints no. This problem is visible in simulators/phones, not on the web mode on the sandbox.
export const AddFriendScreen: React.FunctionComponent = () => {
const initialValues: FormValues = {
input: '',
};
const [showFlatList, setShowFlatList] = useState<UsersLazyQueryHookResult>(
'',
);
const handleSubmitForm = (
values: FormValues,
helpers: FormikHelpers<FormValues>,
) => {
loadUsers({
variables: {
where: {
OR: [
{ phoneNumber: newPhoneNumber },],
},
},
});
helpers.resetForm();
};
return (
<SafeAreaView style={styles.safeAreaViewContainer}>
<View style={styles.searchTopContainer}>
<View style={styles.formContainer}>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.searchFieldContainer}>
<View style={styles.form}>
<FieldInput
handleChange={handleChange}
handleBlur={handleBlur}
value={values.input}
fieldType="input"
icon="user"
placeholderText="E-Mail oder Telefonnummer oder Name"
/>
<ErrorMessage
name="input"
render={(msg) => <ErrorText errorMessage={msg} />}
/>
</View>
<View style={styles.buttonContainer}>
<ActionButton buttonText="Suchen" onPress={handleSubmit} />
</View>
</View>
)}
</Formik>
</View>
<View style={styles.listHolder}>
{data && showFlatList !== null && (
<UsersFoundList
data={data}/>
)}
</View>
</View>
</SafeAreaView>
);
};
Snack Expo:
https://snack.expo.io/#nhammad/jealous-beef-jerky
[![enter image description here][1]][1]
Edit:
Found a workaround but still open to easier solutions. Still haven't figured out what's causing the issue exactly.
However, this causes the formik error to show up immediately after the form is submitted and data is returned/rendered. For instance, the error suggests that the input field should not be empty.
...
Why is the keyboard interfering in this?
Probably because when you call Keyboard.dismiss() you are actually unfocusing the input, which will trigger the validateOnBlur event.
So when you unfocus a field (blur), it will try to validate (validateOnBlur), and because the field is empty, it will show the validation.
The error should only show up when we are trying to resubmit the form without an input.
If the error should only show when you submit the form, you should pass to the Formik component validateOnBlur={false}. With this, it won't show the error message when you call Keyboard.dismiss(), because it will remove the validation on blur.
But it still validates when the input changes, triggering validateOnChange. You can also pass to the Formik component validateOnChange={false} to disable the validation on change and it will only validate when you submit the form (press the button).
Please notice that validateOnChange and validateOnBlur are true by default.
Edit:
You need to add validateOnBlur={false} but instead of calling Keyboard.dismiss() you need to call fieldRef.current.blur(). But for that to work, you need to use React.forwardRef in your input component.
So you need to create the fieldRef
const fieldRef = useRef();
Pass to the component
<FieldInput
ref={fieldRef} // passing the ref
handleChange={handleChange}
handleBlur={handleBlur}
value={values.input}
fieldType="input"
icon="user"
placeholderText="input"
/>
And you need to wrap around your FieldInput with React.forwardRef and pass the ref to the Input component.
// wrapping the component with React.forwardRef
export const FieldInput: React.FunctionComponent<FieldInputProps> = React.forwardRef(({
handleChange,
handleBlur,
fieldType,
placeholderText,
value,
style,
rounded,
//ref,
}, ref) => {
return (
<Item rounded={rounded} style={[styles.inputItem, style]}>
<Input
ref={ref} // passing the ref
autoFocus={true}
autoCapitalize="none"
style={styles.inputField}
placeholder={placeholderText}
keyboardType="default"
onChangeText={handleChange(fieldType)}
onBlur={handleChange(fieldType)}
value={value}
/>
</Item>
);
});
And now in the handleSubmitForm you call fieldRef.current.blur()
const handleSubmitForm = (
values: FormValues,
helpers: FormikHelpers<FormValues>,
) => {
// other logic from your submit
if (fieldRef && fieldRef.current)
fieldRef.current.blur();
};
With this, you will solve the problems of your question and from the comments.
Working example
Explanation about useRef
We need to use the useRef hook so we can get the element of the Input component to be able to call the blur function so don't be focused in the input and can click in the plus button.
We need to use the useRef hook because that is how you create a ref in functional components.

React Hook Form dynamic required

I'm trying to make a form with two fields using react hook form where the required value of the text field depends on the value of the select drop down.
Here is my code:
const { handleSubmit, control, errors } = useForm();
const [isPickupPoint, togglePickupPoint] = useState(false);
const handleDestinationTypeChange: EventFunction = ([selected]) => {
togglePickupPoint(selected.value === "PICKUP_POINT");
return selected;
};
<Grid item xs={6}>
<InputLabel>Destination type</InputLabel>
<Controller
as={Select}
name="destinationType"
control={control}
options={[
{ label: "Pickup point", value: "PICKUP_POINT" },
{ label: "Shop", value: "SHOP" },
]}
rules={{ required: true }}
onChange={handleDestinationTypeChange}
/>
{errors.destinationType && (
<ErrorLabel>This field is required</ErrorLabel>
)}
</Grid>
<Grid item xs={6}>
<Controller
as={
<TextField
label="Pickup Point ID"
fullWidth={true}
disabled={!isPickupPoint}
/>
}
control={control}
name="pickupPointId"
rules={{ required: isPickupPoint }}
/>
{errors.pickupPointId && (
<ErrorLabel>This field is required</ErrorLabel>
)}
</Grid>
<Grid item xs={12}>
<Button
onClick={onSubmit}
variant={"contained"}
color={"primary"}
type="submit"
>
Save
</Button>
</Grid>
The isPickupPoint flag changes properly because the disabled prop of the textfield works fine. Only when the PICKUP_POINT option is selected the text field is active. But the required prop is not working, it is always false. When I try submitting the form when its empty the destinationType error label appears, but when I try to submit the form with the PICKUP_POINT option and empty pickupPointId field it passes with no errors.
How can I make this dynamic required prop work?
Based on the code here, it looks like isPickUpPoint is working as expected since it works for disable. Since you are using the same property for required, it should flow through. I would suspect that the bug may lie in your Controller component. I would take a look there and make sure that the property is what you expected to be.
Also for disabled the condition is !isPickUpPoint, so it will trigger when it's false.
For required the condition is isPickUpPoint so it will trigger when it's true.
That's also a bit of a disconnect since it looks like it's the same input.

Dynamic FormControlLabel Radio inside RadioGroup is not getting checked

I'm using a RadioGroup component to display a dynamic list of Radio options using FormControlLabel. The dynamic radio options are successfully getting displayed and I'm able to retrieve the selected radio option using onChange in RadioGroup. However, when I check a radio option, that particular option does not appear "checked".
Here's the code I'm using:
export default class Book extends Component {
state = {
slot: null,
};
...
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
...
<FormControl component="fieldset" className={classes.formControl}>
<FormLabel component="legend">Select availability slot</FormLabel>
<RadioGroup
aria-label="Slot"
name="slot"
className={classes.group}
value={this.state.slot}
onChange={this.onChange}
>
{this.props.experience.availability !== null ? (
this.props.experience.availability.map(single => (
<FormControlLabel
key={single.id}
value={single.id}
control={<Radio color="primary" />}
label={single.title}
/>
))
) : ""}
// Some manual options in addition to the above dynamic list
<FormControlLabel
value="female"
control={<Radio color="primary" />}
label="Female"
/>
<FormControlLabel
value="male"
control={<Radio color="primary" />}
label="Male"
/>
</RadioGroup>
</FormControl>
...
}
this.props.experience.availability is an array of objects that I'm getting from a call I'm making to the backend. The call is being made in componentDidMount(). I use Redux, which makes the result available as a prop.
Now, if I manually add a few FormControlLabel components in the same RadioGroup, I can see that it's checked after selecting that option.
I've taken two screenshots - this is when the manual FormControlLabel is selected: https://ibb.co/pL5Fy6L and this is when one of my dynamic option is selected: https://ibb.co/Jq7mLN4
You can see that in the second one, the "Female" option gets unchecked but the option that I selected (20-06-2019) does not appear to be checked.
Can you please help me fix this?
Thanks in advance!
For anybody who might be facing the same issue, here's what I was doing wrong.
The problem was that I was passing an integer value to the value prop instead of a string like: value={single.id} so I added toString() to convert it to string like this: value={single.id.toString()} and it's working fine now.

Categories