I have a menu component which I want to close when I click anywhere on the page if it’s open.
Is there a way to close the menu without the need for an event listener being added to the document and checking the event.target.
There is no way to send the close function back upto the parent component as it lives on a separate Route.
Navbar
-> MenuComponent
RouteView
-> MainContent
Yes. This is easily accomplished by wrapping the component in the ClickAwayListener provided by material ui. Then you pass a function to the onClickAway attribute and that should close your menu for you. I've provided a template below and you can also check out the MUI docs:
import ClickAwayListener from '#mui/material/ClickAwayListener';
export default function MenuComponent() {
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen(!open);
};
const handleClickAway = () => {
setOpen(false);
};
return (
<ClickAwayListener onClickAway={handleClickAway}>
<Box>
<button type="button" onClick={handleClick}>
Open menu dropdown
</button>
{open ? (
<Box>
Click me, I will stay visible until you click outside.
</Box>
) : null}
</Box>
</ClickAwayListener>
);
}
Related
I'm prototyping a new version of our application in React 18. I'm somewhat new to React and have stumbled upon a scenario that has a few different problems.
We need to open a modal/dialog when a user performs an action. They will click a button to edit data, that opens a dialog window with a form. When they close the dialog, the form data is passed back to the component which opened it.
In our old app, it would be something like const user = new UserModal(123)
I'm using BlueprintJS's Dialog component for this, but this issue is applicable to any library.
I'm writing a wrapper because all of our modals will have similar functionality so the props to the Dialog component will never change outside of whether it's open or not.
Here's a super basic example of this "wrapper" component:
export const Modal = ({ isOpen }: ModalProps) => {
const [isOpen2, setIsOpen] = useState(isOpen);
const handleClose = useCallback(() => {
setIsOpen(false);
}, []);
return (
<Dialog isOpen={isOpen2}>
<p>this is a dialog</p>
<Button onClick={handleClose} text="close" />
</Dialog>
);
}
Using this in the parent would look like this:
const Demo = () => {
const [isOpen, setIsOpen] = useState(false);
// some code calls setIsOpen(true) when we need to open the modal
return <Modal isOpen={isOpen} />;
}
This presents multiple problems:
A parent controller can trigger this dialog to open, but never close (interacting with the app is prevented while a modal is open)
The modal can close itself via an X or "Cancel" button
This leads to two useState invocations - one in the parent controller and one inside the modal. This doesn't work right by itself, because once the state is set in the controller, it can't update when the prop changes with more code
The parent controller would need to know when it closes so it can update it's own state value.
I really dislike having to put <Modal> elements in the parent jsx, I liked the new UserModal code but that might be a fact of life
Overall, this feels like a very wrong approach. How can I design this to be more "proper" and yet work the way I need?
you can pass your method from parent to child and call there and also you can use 1 state for manage modal status.
export const Modal = ({ isOpen, handleClose, closeCallBack }: ModalProps) => {
const handleCloseChild = () =>{
closeCallBack()
handleClose()
}
return (
<Dialog isOpen={isOpen}>
<p>this is a dialog</p>
<Button onClick={handleCloseChild} text="close" />
</Dialog>
);
}
and parent something like this
const Demo = () => {
const [isOpen, setIsOpen] = useState(false);
// some code calls setIsOpen(true) when we need to open the modal
const handleClose = () =>{
setIsOpen(false)
}
return <Modal isOpen={isOpen} handleClose={handleClose} closeCallBack={() => // do what you want on close modal or you just do this in side modal or even in handelClose function } />;
}
I recently learned a bit about react and I'm confused about how I would transfer data between two components.
Currently, I have 2 functions implemented as such:
I have topbar.tsx which shows the topbar information, such as showing some button to open the sidebar (which is my next function).
import Sidebar from './sidebar'
export default topbar(props) {
return (
<Drawer
open={isOpen}
onclose={anotherfunction}>
<Sidebar />
</Drawer>
)
}
I also have sidebar.tsx which contains the implementation of sidebar. (This is, if I understand react terminology correctly, the child).
import CloseButton from 'react-bootstrap/CloseButton';
export default Sidebar() {
return (
<CloseButton />
)
}
The issue I'm facing is that, since the topbar is the function that controls the opening and closing of the sidebar, however, I want a close button to appear on my sidebar and do exactly as is said. How would I be able to transfer state information between these two files such that I can open sidebar and close it with the button in sidebar.
Thank you!
You elevate your state to the parent component, and pass event handler functions through.
For instance:
// Holds state about the drawer, and passes functions to mamange that state as props.
function Topbar() {
const [isOpen, setIsOpen] = useState(false)
return (
<Drawer isOpen={isOpen}>
<Sidebar onClose={() => setIsOpen(false)} />
</Drawer>
)
}
// Drawer will show it's children if open.
function Drawer({ isOpen, children }: { isOpen: boolean, children: React.ReactNode }) {
if (!isOpen) return null
return <div>{children}</div>
}
// Sidebar will send onClose to the button's onClick
function Sidebar({onClose}: { onClose: () => void }) {
return (
<CloseButton onClick={onClose} />
)
}
// Close button doesn't care what happens on click, it just reports the click
function CloseButton({onClick}: { onClick: () => void }) {
return <div onClick={onClick} />
}
Playground
First of all, rename topbar to Topbar. Otherwise you can't render your component.
For your question, you can pass the props directly to Sidebar component too.
export default Topbar(props) {
return (
<Drawer
open={isOpen}
onclose={anotherfunction}>
<Sidebar open={isOpen}
onclose={anotherfunction}/>
</Drawer>
)
}
im facing this weird behavior when trying to update the parent component with an set function to the child with props
this hook is to open and close the modal to edit an element
//PARENT FILE
//hook
const [isEditModalOpen, setEditModalOpen] = useState(false)
//more code...
//modal
{isEditModalOpen && <EditExcerciseModal setEditModalOpen={setEditModalOpen} isEditModalOpen={isEditModalOpen} />}
and this is the child code
//CHILD FILE
export const EditExcerciseModal = ({setEditModalOpen, excerciseInfo,fetchExcercisesFromRoutine})
//more code etc etc
<div className="addExcerciseModalContainer">
<span onClick={() =>{ setEditModalOpen(false) }} className="xModal">X</span>
i checked and the onClick is working. if i change the parent state manually the Modal works fine and closes.
the weird case when it its working is when instead of calling the set function i create a function with a setTimeout without time like this:
function closeModal(){
setTimeout(() => { setEditModalOpen(false)}, 0);
}
any ideas?
thanks for the help
You need to create a separation of concern. A Modal consists of three parts
The Modal of its Self.
The Content of the Modal.
And the container of the two.
You should be using the useState() hook and calling setEditModalOpen in the same containing component.
You need to make sure that you're declaring and setting state inside the same component.
// children would be the content of the modal
const Modal = ({ children, selector, open, setOpen }) => {
// we need the useEffect hook so that when we change open to false
// the modal component will re-render and the portal will not be created
useEffect(() => {
setOpen(false);
//provide useEffect hook with clean up.
return () => setOpen(true);
}, [selector]);
return open ? createPortal(children, selector) : null;
};
export const EditExerciseModal = ({ close }) => {
return (
<div>
{/* Instead of creating a handler inside this component we can create it in it's parent element */}
<span onClick={close}>X</span>
{/* Content */}
</div>
);
};
export const ModalBtn = () => {
const [isEditModalOpen, setEditModalOpen] = useState(false);
// this is where it all comes together,
// our button element will keep track of the isEditModalOpen variable,
// which in turn will update both child elements
// when true useEffect hook will re-render Modal Component only now it "will" createPortal()
// when our EditExerciseModal alls close it will set change the isEditModalOpen to false
// which will be passed to the Modal component which
// will then cause the component to re-render and not call createPortal()
return (
<>
<button onClick={() => setEditModalOpen(true)}>EditExerciseModal</button>
{setEditModalOpen && (
<Modal
open={isEditModalOpen}
setOpen={setEditModalOpen}
selector={'#portal'}>
<div className='overlay'>
<EditExerciseModal close={() => setEditModalOpen(false)} />
</div>
</Modal>
)}
</>
);
};
So, I got into a situation where I need to trigger the same Material-UI <Popper /> component from multiple different clickable elements.
As is it is described on the Popper component API on Material-UI website, when calling the Popper component you might want to pass a property named anchorEl which defines where the popper will be positioned on the canvas. My intention is for that anchorEl to always be the same, thus the popper will always open at the same position on the UI.
Most of the examples on Material-UI documentation suggest the following approach for using Popper:
const SimplePopper = () => {
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
return (
<div>
<button type="button" onClick={handleClick}>
Toggle Popper
</button>
<Popper open={open} anchorEl={anchorEl}>
<div className={classes.paper}>The content of the Popper.</div>
</Popper>
</div>
);
}
The first problem with this usage is that your code depends on the handleClick function to be called for it to define the anchorEl state value.
The second problem is that, even if you have more than one button element calling the handleClick function, the value on event.currentTarget would vary and the Popper element would show up in different positions of the canvas.
So, the question is:
How could I have two or more buttons triggering the same <Popper /> element in a way it always renders under the same anchorEl.
One way to do it would be to add a ref to the anchor element and pass it to Popper and use a state variable to manage the popper open state. You can then toggle the open state when the trigger elements are clicked.
const App = () => {
const anchorRef = useRef(null)
const [open, setOpen] = useState(false)
const handleClick = () => {
setOpen(!open)
}
return (
<>
<Button onClick={handleClick}>Toggle Popper</Button>
<Button onClick={handleClick} ref={anchorRef}>
Toggle Popper
</Button>
<Popper open={open} anchorEl={anchorRef.current}>
The content of the Popper.
</Popper>
</>
)
}
CodeSandbox
I am trying to open a Modal with a self-created button in React and Material-UI. The button also envokes other funcstions, that is why I need to integrate the "Modal Opening" and the other function within one button. Currently, the modal will not open automatically when clicking on the button.
Do you have any ideas?
This is how I integrated the "CanvasLoadingModal" into the Parent Component:
Step 1. Create a Button that envokes a certain function
return(
<div>
<Button
variant="contained"
fullWidth
className={button1}
onClick={saveButtonHandler}
>
Chatbot in Datenbank <br /> speichern
</Button>
</div>
Step 2. Include the "CanvasLoadingModal" with props = {true} in "saveButtonHandler" function
const saveButtonHandler1 = () => {
const showLoadingModal = () => {
return <CanvasLoadingModal open={true} />;
};
showLoadingModal();
}
You can update the state and let React handle the rendering part and conditionally render the CanvasLoadingModal component when clicked. The same function can also be used to close the modal as well.
const App = ()=> {
const [showModal,setShowModal] = useState(false);
function showModalHandler(){
setShowModal(!showModal);
}
return (
<Button onClick={showModalHandler} />
{showModal ? <CanvasLoadModal open={showModal} />: null }
)
}