Doing my best to hide this sidebar when a user clicks anywhere outside of the focused area.
Currently, when I click outside, the sidebar remains open. If I click in an area within the sidebar that isnt a link, the sidebar closes. Just trying to recreate that effect elsewhere in the app.
Have no idea to implement this in React/JS
Thank you for any help you can send my way.
import React, { useState } from 'react';
import * as FaIcons from 'react-icons/fa';
import * as AiIcons from 'react-icons/ai';
import { Link } from 'react-router-dom';
import { NavbarData } from './NavbarData';
import '../styles/Navbar.css';
import { IconContext } from 'react-icons';
import onClickOutsideHOC from "react-onclickoutside"; //<-- Should I be using this?
function Navbar() {
const [sidebar, setSidebar] = useState(false);
const showSidebar = () => setSidebar(!sidebar);
const hideSidebar = () => setSidebar(onClickOutsideHOC()); //<-- Should I be using this?
return (
<>
<IconContext.Provider value={{ color: '#fff' }}>
<div className='navbar'>
<Link to='#' className='menu-bars'>
<FaIcons.FaBars onClick={showSidebar} />
</Link>
</div>
<nav className={sidebar ? 'nav-menu active' : 'nav-menu'}>
<ul className='nav-menu-items' onClick={showSidebar}>
<li className='navbar-toggle'>
<Link to='#' className='menu-bars'>
<AiIcons.AiOutlineClose />
</Link>
</li>
{NavbarData.map((item, index) => {
return (
<li key={index} className={item.cName}>
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
</li>
);
})}
</ul>
</nav>
//Commented Out My Attempt Below
{/* <nav>
<nav className={sidebar ? 'nav-menu inactive' : 'nav-menu'}>
<ul className='nav-menu-items' onClick={hideSidebar}>
<li className='navbar-toggle'>
<Link to='#' className='menu-bars'>
<AiIcons.AiOutlineClose />
</Link>
</li>
{NavbarData.map((item, index) => {
return (
<li key={index} className={item.cName}>
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
</li>
);
})}
</ul>
</nav>
</nav>*/}
</IconContext.Provider>
</>
);
}
export default Navbar;
For this, you can create a custom hook that takes the element you want as a parameter. I created a sandbox for you.
https://codesandbox.io/s/outside-click-hook-uc8bo
We send the ref of the element to the custom hook we created.
const boxRef = useRef(null);
// boxOutsideClick will be true on outside click
const boxOutsideClick = OutsideClick(boxRef);
<div ref={boxRef} className="box">
<h1>Click outside of me</h1>
</div>
And our hook will look like this:
export default function OutsideClick(ref) {
const [isClicked, setIsClicked] = useState();
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
setIsClicked(true);
} else {
setIsClicked(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
return isClicked;
}
OnMouseDown, we check if the clicked place is in the selected element and update the isClicked value and return.
I got a hack,
add another empty div with the CSS position absolute before the sidebar and give the sidebar the same position absolute and style as you want.
Let the first div have a full view-width and view-hight.
The sidebar can have whatever size you want but it will be on higher in z-index then the first div/sibling.
Add an onClick function on the first div to close the sidebar because when someone clicks outside of the sidebar, it will be a click on the first div.
To close a side navigation bar when the user clicks outside of it in a React application, you can add an event listener to the 'document' object that listens for 'mousedown' events. When the user clicks outside the side navigation bar, the event listener will trigger a function that closes the side navigation bar.
Here's some sample code to achieve this:
import React, { useEffect, useRef } from 'react';
function App() {
const sideNavRef = useRef(null);
useEffect(() => {
// Add event listener to the document object
document.addEventListener('mousedown', handleClickOutside);
// Remove event listener when the component unmounts
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
function handleClickOutside(event) {
if (sideNavRef.current && !sideNavRef.current.contains(event.target)) {
// Clicked outside the side navigation bar, close it
// Implement your close side navigation bar logic here
}
}
return (
<div>
<div ref={sideNavRef}>
{/* Your side navigation bar content goes here */}
</div>
<div>
{/* Other content of your application goes here */}
</div>
</div>
);
}
Explanation:
First, we define a sideNavRef using the useRef hook. This reference will be used to check if the click happened inside or outside of the side navigation bar.
We use the useEffect hook to add an event listener to the document object when the component mounts. The useEffect hook's cleanup function is used to remove the event listener when the component unmounts.
The handleClickOutside function checks if the click happened outside of the side navigation bar using the contains method on the sideNavRef reference. If the click happened outside of the side navigation bar, you can implement your close side navigation bar logic in this function.
Finally, we wrap the side navigation bar content inside a div with the ref attribute set to sideNavRef. The other content of your application goes into a separate div.
Related
let me explain my question.
I would like to create expanding flex cards, here is the exemple on codepen : https://codepen.io/z-/pen/OBPJKK
and here is my code for each button :
basically I have a component which is called HomeButtons that generates every flex cards. Inside this component I have a smaller component called readMore. In this component I have a useState that allows me to toggle individually each button to add or retreive an active class. If the active class is present, that means that the selected button must expand and the other ones must shrink.
What I would like to do is to access the readMore state ouside of the readMore subcomponent. That way I could write a function to remove the active class from a card if the user clicks on another card like so :
function setToUnactive() {
if (readMore(true)) {
readMore(false)}
}
My question is how can I get the state of readMore outside of the readMore subcomponent ? Do I need to use useContext ? Because that seems very simple to do but I tried a lot of things and nothing works. Can I pass the state readMore as a prop of the component ReadMore ? Thank you !
import React, { useState } from 'react';
import '../style/catalogue.scss';
import collectionsItems from '../Components/collectionsItemsData';
import { Link } from "react-router-dom";
const HomeButtons = ({}) => {
function ReadMore() {
const [readMore, setReadMore] = useState(false)
function toggleSetReadMore() {
setReadMore(!readMore)
}
return (
<p className='showmore' onClick={toggleSetReadMore} className={readMore ? "collection-item active" : "collection-item"}>TOGGLE BUTTON</p>
)
}
return <div>
{collectionsItems.map((collectionItem) => {
const { id, category, img } = collectionItem;
return < article key={id} >
<img className="item-img" src={img} alt=''/>
<ReadMore />
<Link to={`/${category}`} className="item-title">{category}</Link>
</article>
})}
</div>
}
export default HomeButtons;
First of all you need extract ReadMore component from function outside!
And for your problem you can lift state up(https://reactjs.org/docs/lifting-state-up.html). And since at the same time only one item can be opened you can do something like this:
function ReadMore({ isOpened, setOpened }) {
return (
<p
onClick={setOpened}
className={isOpened ? "collection-item active" : "collection-item"}
>
TOGGLE BUTTON
</p>
);
}
const HomeButtons = () => {
const [openedItemId, setOpenedItemId] = useState(null);
return (
<div>
{collectionsItems.map((collectionItem) => {
const { id, category, img } = collectionItem;
return (
<article key={id}>
<img className="item-img" src={img} alt="" />
<ReadMore
isOpened={openedItemId === id}
setOpened={() => setOpenedItemId(id)}
/>
<Link to={`/${category}`} className="item-title">
{category}
</Link>
</article>
);
})}
</div>
);
};
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>
);
}
i am working with negomi/react-burger-menu. i want to close my sidebar menu when a link is click not outside or cross button click just link click then my sidebar menu automatically close itself. But problem is my link is under another component , Suppose my component <ShipForMe/> and my link is under <ShipForMe/> like <NavLink to="/dashboard/ship-for-me/my-request/pending">My Request</NavLink>
Sidebar.js
<div className="sidebar-wrap " id="outer-container">
<div className="dashboard-menu rounded">
<ShipForMe/>
<MyWallet/>
<Profiles/>
<div className="sidebar-item ds-item">
<div className="sidebar-item__title">
<NavLink to="">VIP Center</NavLink>
</div>
</div>
</div>
<div className="dashboard-responsive rounded">
<Menu pageWrapId={ "page-wrap" } outerContainerId={ "outer-container" } isOpen={false}>
<div id="page-wrap" style={{marginTop: '-25%'}}>
<ShipForMe handleUrl={handleUrl}/>
<MyWallet/>
<Profiles/>
<div className="sidebar-item ds-item">
<div className="sidebar-item__title">
<NavLink to="">VIP Center</NavLink>
</div>
</div>
</div>
</Menu>
</div>
</div>
ShipForMe.js
<div className="ship-wrap ds-item">
<div className="sidebar-item">
<div className="sidebar-item__title">
Ship for me
</div>
<ul className="sidebar-item__lists">
<li>
<NavLink to="/dashboard/ship-for-me/my-request/pending">My Request</NavLink>
</li>
<li>
<NavLink to="/dashboard/ship-for-me/forwarding-parcel/abroad-to-bd">My Forwarding Parcel</NavLink>
</li>
</ul>
</div>
</div>
Note: isOpen{flase} is not working, it is only working when link is physical there.
First stack overflow response so, please bear with me.
Also, seems like I'm a bit late but hopefully this can help others.
In your Menu component, add: (1) onOpen={handleOpen}, (2) onClose={handleOpen}, and (3) isOpen={isOpen} properties.
onOpen will trigger when the sidebar nav menu is opened and we want
it to set isOpen to true
onClose will trigger whenever the user clicks on the cross icon, overlay, or escape key. (Assuming none of these have been disabled/modified)
isOpen controls whether the sidebar nav menu is rendered open (true) or close (false).
In your ShipForMe component add a reference to a function that will set isOpen to false. In my example ShipForMe is SideBarLinks. And the function is closeSideBar
Here is my example:
const [isOpen, setOpen] = useState(false)
const handleIsOpen = () => {
setOpen(!isOpen)
}
const closeSideBar = () => {
setOpen(false)
}
<Menu
isOpen={isOpen}
onOpen={handleIsOpen}
onClose={handleIsOpen}
>
<SideBarLinks closeSideBar={closeSideBar} />
</Menu
const SideBarLinks = ({ closeSideBar }) => {
return (
<>
<NavLink to="/#about" onClick={closeSideBar}>
About
</NavLink>
</>
Please do let me know if this helps and any feedback is appreciated.
Additional info: react version 17.0.1, react-burger-menu version 3.0.6
References: https://www.npmjs.com/package/react-burger-menu
Have spent a lot of time fiddling with problem. One critical part of the solution is to pass the appropriate callback function as a prop to the < Menu > component:
onStateChange={handleStateChange}
where handleStateChange is defined as:
const handleStateChange = (state) => {
setMenuOpen(state.isOpen);
};
where setMenuOpen is state setter as defined in:
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
you also need a handleCloseMenu function that calls your setter:
const handleCloseMenu = () => {
setIsMenuOpen(false);
};
you can then pass handleCloseMenu to an onclick of a button in your sidebar / burger menu.
all together you have:
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const handleCloseMenu = () => {
setIsMenuOpen(false);
};
const handleStateChange = (state) => {
setIsMenuOpen(state.isOpen);
};
return (
<Menu isOpen={isMenuOpen} onStateChange={handleStateChange}>
... Link 1
<button onClick = {()=>handleCloseMenu}/>
....
</Menu>
)
Here is a working sandbox example I found on the github repo:
And here is the github repo. There's an Issue posted about streamlining it further. But once you pass the correct callback, onStateChange={handleStateChange}, you should be good.
I have a test site HERE
Please do:
Visit the site and click on the hamburger icon in the top right. The
side nav should open.
Perform the same action again and you will see the problem I am
currently having.
The side nav does not close properly because I have two conflicting functions operating.
The first conflicting function is an onClick toggle function within the actual hamburger component which toggles an associated context state.
The second conflicting function is used by the side nav panel component. It is bound to a hook which uses its ref to check whether the user has clicked inside or outside of the component.
These functions work up until the user clicks on the hamburger menu whilst the side nav is open. The hamburger is technically outside of the side nav component but overlayed on top of it, so the click does not register as outside. This results in both functions firing one after the other. The "click outside" hook fires and sets the side nav context to false, closing it. The hamburger toggle function then fires, finds the context set to false and changes it back to true. Thus instantly closing and then reopening the side nav.
The solution I have attempted looks like the below. I tried to assign a ref within the hamburger child component and pass it to the side nav parent. I wanted to try and compare the callback returned from useClickedOutside to the toggleRef given to the parent but the hamburger component and then if they match then do nothing. This, I was hoping, would knock one of the functions out of action for that interaction. Not entirely sure this is the best way to attempt to achieve something like this.
Perhaps I could get the side nav bounding box and then check if the click coordinates land within it?
//SideNav.jsx
const SideToggle = ({ sideNavToggle, sideIsOpen, setToggleRef }) => {
useEffect(() => {
const toggleRef = React.createRef();
setToggleRef(toggleRef);
}, []);
return (
<Toggle onClick={sideNavToggle}>
<Bars>
<Bar sideIsOpen={sideIsOpen} />
<Bar sideIsOpen={sideIsOpen} />
<Bar sideIsOpen={sideIsOpen} />
</Bars>
</Toggle>
);
};
export default function SideNav() {
const [toggleRef, setToggleRef] = useState(null);
const { sideIsOpen, sideNavToggle, setSideIsOpen } = useContext(
NavigationContext
);
const handlers = useSwipeable({
onSwipedRight: () => {
sideNavToggle();
},
preventDefaultTouchmoveEvent: true,
trackMouse: true,
});
const sideRef = React.createRef();
useClickedOutside(sideRef, (callback) => {
if (callback) {
if (callback === toggleRef) {
return null;
} else setSideIsOpen(false);
}
});
return (
<>
<SideToggle
sideIsOpen={sideIsOpen}
sideNavToggle={sideNavToggle}
setToggleRef={setToggleRef}
/>
<div ref={sideRef}>
<Side sideIsOpen={sideIsOpen} {...handlers}>
<Section>
<Container>
<Col xs={12}>{/* <Menu /> */}</Col>
</Container>
</Section>
</Side>
</div>
</>
);
}
The useClickedOutside hook used above.
//use-clicked-outside.js
export default function useClickedOutside(ref, callback) {
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
callback(event.target);
} else return null;
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
}
EDIT
Managed to fix part of the problem by moving the toggle component inside of the side nav component. It now has its own problem in that the hamburger component completely rerenders when the side nav props change. Trying to figure out how to stop the hamburger rerendering in a hooks scenario such as this. I've been reading that React.memo may be the answer but unsure how to implement it as of yet.
//SideNav.jsx
export default function SideNav() {
const { sideIsOpen, sideNavClose, sideNavOpen } = useContext(
NavigationContext
);
const handlers = useSwipeable({
onSwipedRight: () => {
sideNavClose();
},
preventDefaultTouchmoveEvent: true,
trackMouse: true,
});
const sideRef = React.createRef();
useClickedOutside(sideRef, (callback) => {
if (callback) {
sideNavClose();
}
});
const SideToggle = () => {
const handleClick = () => {
if (!sideIsOpen) {
sideNavOpen();
}
};
return (
<Toggle onClick={() => handleClick()}>
<Bars>
<Bar sideIsOpen={sideIsOpen} />
<Bar sideIsOpen={sideIsOpen} />
<Bar sideIsOpen={sideIsOpen} />
</Bars>
</Toggle>
);
};
return (
<>
<SideToggle />
<div ref={sideRef}>
<Side sideIsOpen={sideIsOpen} {...handlers}>
<Section>
<Container>
<Col xs={12}>{/* <Menu /> */}</Col>
</Container>
</Section>
</Side>
</div>
</>
);
}
I'd recommend using this package: https://github.com/airbnb/react-outside-click-handler . It handles some edge cases you mention, you can check their source code if you are interested in the details.
Then you need to keep the information about whether the sidebar is opened in the state and pass it down to the affected components somewhat like this (this way you can also change how the site looks in other places depending on the toolbar state, eg. disable scrollbar etc):
function Site() {
const [isOpened, setToolbarState] = React.useState(false);
return (
<React.Fragment>
<Toolbar isOpened={isOpened} setToolbarState={setToolbarState} />
{isOpened ? (<div>draw some background if needed</div>) : null}
<RestOfTheSite />
</React.Fragment>
);
}
function Toolbar(props) {
const setIsClosed = React.useCallback(function () {
props.setToolbarState(false);
}, [props.setToolbarState]);
const setIsOpened = React.useCallback(function () {
props.setToolbarState(true);
}, [props.setToolbarState]);
return (
<OutsideClickHandler onOutsideClick={setIsClosed}>
<div className="toolbar">
<button onClick={setIsOpened}>open</button>
...
{props.isOpened ? (<div>render links, etc here</div>) : null}
</div>
</OutsideClickHandler>
);
}
This way you won't necessary need to juggle refs and handlers around too much.
The way I answered this was to avoid detecting a click outside of the side nav altogether and instead detected if the click was outside of the main body of the site.
If true, then I call the navigation context and set side nav to false the same as I was trying in the side nav itself.
I wanted to hide menu slider onclicking a body in reactjs. how can i do that in function using react.js.
document.querySelector('.open-menu').onclick = function(){
html.classList.add('active');
};
document.querySelector('.close-menu').onclick = function(){
html.classList.remove('active');
};
html.onclick = function(event){
if (event.target === html){
html.classList.remove('active');
}
};
I want this same functionality in react js.
Check the code below.
import React, { useState } from 'react';
const SomeComponent = () => {
const [isMenuOpen, showMenu] = useState(false)
const toggleMenu = () => showMenu(!isMenuOpen)
return (
<>
{isMenuOpen && <MenuCompoment />}
<div onClick={toggleMenu}><App /></div>
</>
)
}
This is a stripped down version of code I've used before.
UseEffect on mounting of the Menu adds an event listener on the document for the click event.
When a click happens it uses closest to look up the parent tree of elements for an id (note the '#')
If it finds one, then the click happened on the menu otherwise it happened on any other element so close.
When the menu is disposed the return function of useEffect is called and removes the event handler.
import React, {useState, useEffect} from 'react';
const Page = () => {
const [toggle, setToggle] = useState(false);
return <div>
<button type="button" onClick={e => setToggle(!toggle)}>Toggle</button>
{ toggle && <Menu show={toggle} hide={() => setToggle(false)}/>}
</div>
}
const Menu = ({show, hide}) => {
useEffect(() => {
document.addEventListener("click", listen);
return () => {
document.removeEventListener("click", listen);
}
}, []);
const listen = e => {
if (!e.target.closest("#menu")) {
hide();
}
}
return <div className="menu" id="menu">
<span>I'm a menu</span>
</div>;
}
i think setting onclick event on the menuItems like this will Work
onClick={()=> setOpen(!open)}
export function SidebarMenu({open, setOpen}) {
return (
<div open={open}>
<Link to="#" title="Home" onClick={()=> setOpen(!open)} >Home</Link>
</div>
)
}
Probably too late for answer but since I saw it in active feed, I will try my best to answer it.
I can see that if your menu is open, you want to hide it if clicked anywhere else. I have used useRef to store the menu node and I compare it to the document whenever its open, if it is, I close the menu
Codesandbox link