TextField in MenuItem causes nextFocus.getAttribute is not a function - reactjs

I have created a Menu with Material UI that pops up when a button gets clicked. This Menu displays a dynamic list of id's that can get filtered with a Textfield positioned at the top of the Menu. But when I'm typing inside of the textfield I get the error: Uncaught TypeError: nextFocus.getAttribute is not a function.
I have no Idea what could cause this issue and how it can be resolved.
This is the code of my SearchableFilter component that is shown on the image above. I have removed some unnecessry styling in the code
const SearchableFilter = ({ values, label, setState, state }) => {
const classes = useStyles();
const [focused, setFocused] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const [searchValue, setSearchValue] = useState("");
const openMenu = (event) => {
setFocused(true);
setAnchorEl(event.currentTarget);
};
const selectItem = (value) => {
handleClose();
setState(value);
};
const handleDelete = () => {
setState("");
};
const handleChange = (event) => {
return setSearchValue(event.target.value);
};
const filteredValues = values.filter((value) =>
value.includes(searchValue.trim())
);
const handleClose = () => {
setFocused(false);
setAnchorEl(null);
};
return (
<div className={classes.margin}>
<Button
aria-controls="simple-menu"
aria-haspopup="true"
onClick={openMenu}
disableRipple
endIcon={<ArrowDropDownIcon color={state && "primary"} />}
>
{label}
</Button>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
getContentAnchorEl={null}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
open={Boolean(anchorEl)}
elevation={2}
onClose={handleClose}
>
<MenuItem
button={false}
dense={true}
key="input"
className={classes.menuItem}
>
<TextField
id="input"
label="Search"
value={searchValue}
onChange={handleChange}
/>
</MenuItem>
<List style={{ maxHeight: "300px", overflow: "auto" }}>
{filteredValues.map((value) => (
<MenuItem
key={value}
dense={true}
className={classes.menuItem}
disableGutters={true}
onClick={() => selectItem(value)}
>
{value}
</MenuItem>
))}
</List>
</Menu>
</div>
);
};
Full error:
Uncaught TypeError: nextFocus.getAttribute is not a function
at moveFocus (MenuList.js:76)
at handleKeyDown (MenuList.js:188)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
at invokeGuardedCallback (react-dom.development.js:4056)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4070)
at executeDispatch (react-dom.development.js:8243)
at processDispatchQueueItemsInOrder (react-dom.development.js:8275)
at processDispatchQueue (react-dom.development.js:8288)
at dispatchEventsForPlugins (react-dom.development.js:8299)
at react-dom.development.js:8508
at batchedEventUpdates$1 (react-dom.development.js:22396)
at batchedEventUpdates (react-dom.development.js:3745)
at dispatchEventForPluginEventSystem (react-dom.development.js:8507)
at attemptToDispatchEvent (react-dom.development.js:6005)
at dispatchEvent (react-dom.development.js:5924)
at unstable_runWithPriority (scheduler.development.js:468)
at runWithPriority$1 (react-dom.development.js:11276)
at discreteUpdates$1 (react-dom.development.js:22413)
at discreteUpdates (react-dom.development.js:3756)
at dispatchDiscreteEvent (react-dom.development.js:5889)
Edit:
Here is a CodeSandBox that displays the error:

Answer provided by #Ryan Cogswell in the comments.
Adding the onKeyDown prop to the parent MenuItem of the TextField fixed my issue.
<MenuItem
button={false}
onKeyDown={e => e.stopPropagation()}
>
<TextField
onChange={handleChange}
value={value}
/>
</MenuItem>

As answere by JonasLevin.
If you have any div or other element wrapping Textfield , you should add
onKeyDown={e => e.stopPropagation()}
to that element

Related

How do I add a custom attribute to the generated React Menu item?

I'd to add a id or some attribute to the MenuItem, something like <MenuItem id="foo" onClick={handleClose}>Copy Ctrl+C</MenuItem> so that I can acess it from event.target in the context menu event handler in my TreeView.
Currently the code look like this:
export default function FileSystemNavigator() {
const [contextMenu, setContextMenu] = React.useState<{
mouseX: number;
mouseY: number;
} | null>(null);
const handleContextMenu = (event: React.MouseEvent) => {
// get the HTML element with the id, e.g., id="foo" in the attributes of event.target
event.preventDefault();
setContextMenu(
contextMenu === null
? {
mouseX: event.clientX + 2,
mouseY: event.clientY - 6
}
: // repeated contextmenu when it is already open closes it with Chrome 84 on Ubuntu
// Other native context menus might behave different.
// With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus.
null
);
};
const handleClose = () => {
setContextMenu(null);
};
return (
<div onContextMenu={handleContextMenu} style={{ cursor: "context-menu" }}>
<TreeView
aria-label="file system navigator"
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: "auto" }}
>
<TreeItem nodeId="1" label="Applications">
<TreeItem nodeId="2" label="Calendar" />
</TreeItem>
<TreeItem nodeId="5" label="Documents">
<TreeItem nodeId="10" label="OSS" />
<TreeItem nodeId="6" label="MUI">
<TreeItem nodeId="8" label="index.js" />
</TreeItem>
</TreeItem>
</TreeView>
<Menu
open={contextMenu !== null}
onClose={handleClose}
anchorReference="anchorPosition"
anchorPosition={
contextMenu !== null
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
: undefined
}
>
<MenuItem onClick={handleClose}>Copy Ctrl+C</MenuItem>
<MenuItem onClick={handleClose}>Delete</MenuItem>
<MenuItem onClick={handleClose}>Move</MenuItem>
<MenuItem onClick={handleClose}>Email</MenuItem>
</Menu>
</div>
);
}
Here's a live code example
There are a few options to achieve this:
by passing a different function to each of your MenuItem:
const handleCopy = () => {
// Do whatever you need
handleClose();
};
const handleDelete = () => {
// Do whatever you need
handleClose();
};
<MenuItem onClick={handleCopy}>Copy Ctrl+C</MenuItem>
<MenuItem onClick={handleDelete}>Delete</MenuItem>
you can use data- attributes:
const handleClose = (event) => {
const { action } = event.target.dataset
// Here can be a switch/case statement to iterate the action
setContextMenu(null);
};
<MenuItem data-action="copy" onClick={handleClose}>
Copy Ctrl+C
</MenuItem>
just use the inline function:
<MenuItem onClick={() => handleClose({action: 'copy'})}>Copy Ctrl+C</MenuItem>
One way you can have a span tag and pass an id to it inside your MenuItem like this :
<MenuItem onClick={handleClose}>
<span id="Copy Ctrl+C">Copy Ctrl+C </span>
</MenuItem>
<MenuItem onClick={handleClose}>
<span id="Delete">Delete </span>
</MenuItem>
<MenuItem onClick={handleClose}>
<span id="Move">Move </span>
</MenuItem>
<MenuItem onClick={handleClose}>
<span id="Email">Email </span>
</MenuItem>

useState not updating data when passing from parent function component using ref

I am trying to send data to child function component where I am binding form fields with that data. It works fine on first call, but when I am calling 2nd time the data never update in state, its always shows the first one.
This is parent which use the ref of child component
export default function Form1() {
const [count, setCount] = useState(0);
const [counter, setCounter] = useState(10);
const AddNewRef = useRef();
const clickMe=() => {
setCount(count+1);
setCounter(counter*2);
AddNewRef.current.showDrawer(counter*2);
}
return (
<div>
<p>You clicked count: {count} & counter: {counter} times</p>
{
count > 10 ?
(
<p className='red'>your count is greater then 10</p>
) :
(
<p className='green'>your count is less then 10</p>
)
}
<button onClick={() => clickMe()}>
Click me
</button>
<AddNew ref={AddNewRef} Count={count} Counter={counter} />
</div>
)
}
This is child component
const AddNew=forwardRef((props, ref) => {
const[objCounter, setobjCounter] = useState(null);
useImperativeHandle(
ref,
() => ({
showDrawer(count) {
setobjCounter(count);
//only shows at first click at parent, Not updating on 2nd, 3rd click from parent and so on....
}
}),
)
return (
<>
<Drawer
title={<span> <i className='fa-solid fa-kaaba' /> Haj Setup Form</span>}
width={window.innerWidth > 900 ? 800 : window.innerWidth - 50}
onClose={onClose}
visible={visible}
bodyStyle={{ paddingBottom: 80 }}
extra={
<Space>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={onClose} type="primary">
Submit
</Button>
</Space>
}
>
<Form
style={{display: formVisible ? 'block' : 'none'}}
form={form}
layout="vertical"
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
hideRequiredMark>
<Row gutter={16}>
<Col xs={24} sm={24} md={24} lg={24}>
<Form.Item
name="packageName"
label="Package Name"
rules={[{ required: true, message: 'Please enter package name' }]}
initialValue={objCounter}
>
<Input style={{width: '100%'}}
maxLength={100} />
</Form.Item>
</Col>
</Row>
</Form>
</Drawer>
</>
)
});
export default AddNew
Since the state updates are working and you are simply wanting to update the form field, you can use the returned form reference from the useForm hook to update the form state. In this case, update the packageName field.
const AddNew = forwardRef((props, ref) => {
const [objCounter, setobjCounter] = useState(13);
const [visible, setVisible] = useState(false);
const [formVisible, setformVisible] = useState(true);
const [form] = Form.useForm();
useImperativeHandle(ref, () => ({
showDrawer(count) {
setobjCounter(count);
setVisible(true);
form.setFieldsValue({
packageName: count // <-- update the specific field
});
}
}));
const onClose = () => {
setVisible(false);
};
return (
...
);
});

Open a modal (or dialogue) from a dropdown asking the user if they would like to take an action

I would like to open a modal (or dialogue) when a user selects an option from a dropdown menu.
Eventually there will be a few options in the dropdown, and different dialogues/modals will be called and rendered depending on which dropdown option has been clicked. For now - how do I get the modal/dialogue to open with dropdown menu events?
I'm currently using the handleClose handler to attempt to open a dialogue since that event should be easy to grab and use right out of the docs for MUI Menu and MenuItem.
The origination call to the DropdownMenu component (which works well and shows the dropdown menu) occurs in a table through the click of an icon
<DropdownMenu options={['Show modal or dialogue to the user']}>
<MoreHorizIcon />
</DropdownMenu>
The DropdownMenu component (also working well itself, except for not triggering the dialogue/modal) looks like this
interface IProps extends Omit<unknown, 'children'> {
children: any;
options: string[];
}
const ITEM_HEIGHT = 48;
const DropdownMenu = ({ children, options }: IProps) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const showModal = () => {
return (
<AlertDialog />
)
}
const handleClose = () => {
//the native alert dialogue works well
alert('I want to show a dialog or modal the same way this alert shows up and ask the user if they are sure they want to delete something')
//why isn't the custom alert dialog being called?
showModal();
setAnchorEl(null);
};
return (
<div>
<IconButton
aria-label="more"
id="more-button"
aria-controls="long-menu"
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
{children}
</IconButton>
<Menu
id="dropdownmenu"
MenuListProps={{
'aria-labelledby': 'more-button'
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: '20ch'
}
}}
>
{options.map(option => (
<MenuItem key={option} onClick={handleClose} >
{option}
</MenuItem>
))}
</Menu>
</div>
);
};
export default DropdownMenu;
And the example starter dialogue I am using to get the ball rolling looks like this
const AlertDialog = () => {
const [open, setOpen] = React.useState(true);
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Sweet Filler Dialog"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>NO</Button>
<Button onClick={handleClose} autoFocus>
YES
</Button>
</DialogActions>
</Dialog>
</div>
);
}
You can use a state variable to trigger the modal in the DropdownMenu component, whenever you wanted to show the dialog/modal.
const [isModalOpen, setIsModalOpen] = React.useState(false);
and then in the handleClose, you can update the modal state to open the modal.
const handleClose = () => {
setIsModalOpen(true)
setAnchorEl(null);
};
Then somewhere in your JSX of DropdownMenu, you can conditionally render the AlertDialog component like this
{isModalOpen ? <AlertDialog open={isModalOpen} setOpen={setIsModalOpen} /> : null}
Finally, update your AlertDialog component to use props to handle the closing of the modal.
const AlertDialog = ({ open, setOpen }) => {
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Sweet Filler Dialog"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>NO</Button>
<Button onClick={handleClose} autoFocus>
YES
</Button>
</DialogActions>
</Dialog>
</div>
);
}

React - how to invoke popup window in my case?

I'm not a React expert yet thus I have a question for you - how to invoke my popup window from:
import {options, columns,convertToArray} from './consts'
const index = () => {
const {data, loading, error, performFetch} = fetchHook({path: "/xxx/yyy", fetchOnMount: true})
return (
<div className={classes.Container}>
<h1>List of products</h1>
<Divider className={classes.Divider} />
<ProductTable data={convertToArray(data)} options={options} columns={columns}/>
</div>
)
}
export default index;
consts.js
export const actions = (productPropertyId, showModal) => {
const productDetails = (productPropertyId) => {
}
const removeProduct = (productPropertyId, showModal) => {
actions(productPropertyId, showModal);
return (
<div className={classes.actionsContainer}>
<Button
onClick={() => productDetails(productPropertyId)}
> {"More"}
</Button>
<Button
const removeProduct = (productPropertyId, showModal) => {
actions(productPropertyId, showModal);
>{"Remove"}
</Button>
</div>
)
};
export const convertToArray = (productList) => {
let products = []
if (productList != null) {
productList.map(product => {
column1, column2, column3, actions(product.id)]
products.push(prod)
})
}
return products;
};
My popup is --> <FormDialog/> based on react Materials.
Is it possible to invoke popup in this place?
I have a react material table with some columns. The last column contains 2 buttons, one of them is "Remove" (row). Here I want to invoke my popup. Maybe I should rebuild my structure?
UPDATE
Below is my popup - I wonder how to run this popup from the place above:
const formDialog = (popupOpen) => {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
{/*<Button variant="outlined" color="primary" onClick={handleClickOpen}>*/}
{/* Open alert dialog*/}
{/*</Button>*/}
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We will send updates
occasionally.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Email Address"
type="email"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default formDialog;
UPDATE 2
I updated my code taking into cosideration the tips from your response, see above. Can I add a parameter showModal in my export const actions = (productPropertyId, showModal) and then invoke this component with different showModal value? UNfortunately my popup doesn't appear when I click on Remove button :(
You can invoke it conditionally and controle it using some state variable. Like this:
const [removeModal, removeToggle] = useState(false);
return (<>
<div className={classes.actionsContainer}>
<Button
onClick={() => productDetails(productPropertyId)}
> {"More"}
</Button>
<Button
onClick={() => removeToggle(true)}
>{"Remove"}
</Button>
</div>
{removeModal ? <YourComponent /> : ''}
</>
)
I'm using a react fragment there <></> just to position the modal div outside the main div, but you can also invoke it inside your main div as well (I usually do this and set the position: fixed so the modal/popup coud appear in top of everything).

How to access props in function?

I have problem with accessing props from function.
Here is my code(the component where all this is contained is also a function):
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event) {
setAnchorEl(event.currentTarget);
}
function handleClose(val) {
setAnchorEl(null);
this.props.updateSelected(val)
}
return (
<div>
<IconButton onClick={handleClick}>
<FilterListIcon/>
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={() => {handleClose(1).bind(this)}}>Содержит текст</MenuItem>
<MenuItem onClick={() => {handleClose(2)}}>Не содержит текст</MenuItem>
<MenuItem onClick={() => {handleClose(3)}}>Начинается с</MenuItem>
<MenuItem onClick={() => {handleClose(4)}}>Заканчивается на</MenuItem>
</Menu>
</div>
I have tried binding as you see but it did't work.
Here is how I pass this prop to the component from another component:
<FilterOptions updateSelected={this.updateSelectedValue}/>
And the prop itself:
updateSelectedValue = async (val) => {
await this.setState({selectedValue: val});
console.log(this.state.selectedValue)
};
You can destructure the prop from the functional component arguments. And since all your other functions are defined inside your component, you can access that prop from anywhere.
I'll give your component a name of Test:
const Test = ({ updateSelected }) => {
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event) {
setAnchorEl(event.currentTarget);
}
function handleClose(val) {
setAnchorEl(null);
updateSelected(val)
}
return (
<div>
<IconButton onClick={handleClick}>
<FilterListIcon/>
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={() => {handleClose(1).bind(this)}}>Содержит текст</MenuItem>
<MenuItem onClick={() => {handleClose(2)}}>Не содержит текст</MenuItem>
<MenuItem onClick={() => {handleClose(3)}}>Начинается с</MenuItem>
<MenuItem onClick={() => {handleClose(4)}}>Заканчивается на</MenuItem>
</Menu>
</div>
}

Resources