I'm using the material-ui lib and I need to have an autocomplete where each item inside that autocomplete is clickable and opens a modal.
The structure in general is:
const ModalBtn = () => {
...
return (
<>
<button ... (on click set modal to open)
<Modal ...
</>
);
}
const AutoCompleteWithBtns = () => {
return (
<Autocomplete
renderTags={(value, getTagProps) =>
value.map((option, index) => <ModalBtn />)
}
...
/>
);
}
Note that the ModalBtn is a component that cannot be divided into two components of Button and Modal.
The issue is that when you click on the button inside the modal - the focus is kept inside the autocomplete, and the modal will never gets the focus (if I have an input inside the modal - I can't write anything inside).
Tried all the standard autocomplete/modal focus-related props (disableEnforceFocus
, disableEnforceFocus
, etc...) but nothing works.
Here is a working codesandbox example. As you can see - if you click on the button that is not inside the autocomplete component - everything works (you can add text inside the input field). If you click on the button that is inside the autocomplete - the input field inside the modal is not editable (you lose focus).
Here is an example of the issue:
The problem with having the Modal
rendered from within the Autocomplete
is that events propagate from the Modal
to the Autocomplete
. In particular, click and mouse-down events are both handled by Autocomplete
in a manner that causes problems in your case. This is primarily logic intended to keep focus in the right place as you interact with different parts of the Autocomplete
.
Below (from https://github.com/mui-org/material-ui/blob/v4.9.11/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js#L842) is the portion of the Autocomplete
code that is getting in your way:
// Prevent input blur when interacting with the combobox
const handleMouseDown = (event) => {
if (event.target.getAttribute('id') !== id) {
event.preventDefault();
}
};
// Focus the input when interacting with the combobox
const handleClick = () => {
inputRef.current.focus();
if (
selectOnFocus &&
firstFocus.current &&
inputRef.current.selectionEnd - inputRef.current.selectionStart === 0
) {
inputRef.current.select();
}
firstFocus.current = false;
};
The default browser behavior when a mouse down event occurs on a focusable element is for that element to receive focus, but the mouse-down handler for Autocomplete
calls event.preventDefault()
which prevents this default behavior and thus prevents a focus change from the mouse-down event (so focus stays on the Modal
itself as indicated by its blue focus outline). You can however successfully move focus to the Modal's TextField using the tab key, since nothing is preventing that mechanism of focus change.
The Autocomplete
click handler is keeping focus on the input of the Autocomplete
even if you click some other part of the Autocomplete
. When your Modal
is open, the effect of this is that when you click in the Modal
, focus is moved briefly to the Autocomplete
input element, but the focus is immediately returned to the Modal
due to its "enforce focus" functionality. If you add the disableEnforceFocus
property to the Modal
, you'll see that when you click in the Modal
(e.g. on the TextField) the cursor remains in the input of the Autocomplete
.
The fix is to make sure that these two events do NOT propagate beyond the Modal
. By calling event.stopPropagation()
for both the click and mouse-down events on the Modal
, it prevents the Autocomplete
functionality for these two events from being executed when these events occur within the Modal
.
<Modal
onClick={event => event.stopPropagation()}
onMouseDown={event => event.stopPropagation()}
...
Related answer: How can I create a clickable first option in Material UI Labs Autocomplete
The problem in your code was that the Modal was rendered from within the tag of the AutoComplete
component, which was not ok because of the visibility of the components, the hierarchy of the components was the issue.
The fix is to move the Modal
within the FixedTags
component and pass the open handler to the ModalBtn
in the renderTags
prop;
I've updated your sandbox with a working variant HERE
The changes are below
const ModalBtn = ({ handleOpen }) => (
<button type="button" onClick={handleOpen}>
Open Modal (not working)
</button>
);
const FixedTags = function() {
const classes = useStyles();
const [modalStyle] = React.useState(getModalStyle);
const [open, setOpen] = React.useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<Autocomplete
multiple
options={autoCompleteItems}
getOptionLabel={option => option.title}
defaultValue={[autoCompleteItems[1], autoCompleteItems[2]]}
renderTags={(value, getTagProps) =>
value.map((option, index) => <ModalBtn handleOpen={handleOpen} />)
}
style={{ width: 500 }}
renderInput={params => (
<TextField
{...params}
label="Fixed tag"
variant="outlined"
placeholder="items..."
/>
)}
/>
<Modal open={open} onClose={handleClose}>
<div style={modalStyle} className={classes.paper}>
<h2 style={{ color: "red" }}>This one doesn't work</h2>
<p>Text field is not available</p>
<TextField label="Filled" variant="filled" /> <br />
<br />
<br />
<FixedTags label="Standard" />
</div>
</Modal>
</>
);
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With