How to close react-burger-menu when link click in a component? - javascript

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.

Related

Show modal based on post id

I am working on my blog and am trying to implement Sanity. I was able to get my posts to show up with the json object returned from query with useState
I am trying to populate my React-Modal with the correct contents based on the post I have clicked with its _id or some kind of key. I simplified the code so it wouldn't be too long:
export default function News() {
// Json objects stored in posts
const [posts, setPosts] = useState([]);
// Used to toggle Modal on and off
const [isOpen, setIsOpen] = useState(false);
function toggleModal() {
setIsOpen(!isOpen);
}
return (
<>
{posts.map((posts) => (
<div key={posts._id}>
<h3 className="title" onClick={toggleModal}>
{posts.title}
</h3>
<div">
<a>
<span onClick={toggleModal}>Read More</span>
</a>
</div>
// Clicking on either span or a tag shows the Modal
<Modal
isOpen={isOpen}
onRequestClose={toggleModal}>
// Closes modal
<button className="close-modal" onClick={toggleModal}>
<img
src="assets/img/svg/cancel.svg"
alt="close icon"/>
</button>
// Want to show content based on _id
<h3 className="title">{posts.title}</h3>
<p className="body">{posts.body}</p>
</div>
)
</>
)
}
Whenever I click on a certain post, it always toggles on the first object.
Click to see gif demo
Edit: I was able to get it to work based on the answer given
const [state, setState] = useState({ isOpen: false, postId: null });
const openModal = React.useCallback(
(_key) => () => {
setState({ isOpen: true, postId: _key });
},
[]
);
function closeModal() {
setState({ isOpen: false, postId: null });
}
And with Modal tag I added
key={post.id == state.postId}
Now every divs and tags that renders the correct content.
However, I'm facing a slight issue. If I click on post[2] and it renders out post[0] content and in a blink of an eye changes to the correct content. Then when I click on post1, it renders and post[2] content and changes to the correct one. It keeps rendering the previous post. It's all in a blink of an eye, but still visible.
I can suggest using react hooks to solve your problem.
You can pass a function to useCallback's return, you can then call your function normally in the render by passing params to it.
See more: https://reactjs.org/docs/hooks-reference.html#usecallback
import * as React from 'react';
export default function News() {
// Json objects stored in posts
const [posts, setPosts] = useState([]);
// Used to toggle Modal on and off
const [isOpen, setIsOpen] = useState(false);
// React.useCallback.
const toggleModal = React.useCallback((id) => () => {
setIsOpen(!isOpen);
console.log(`Post id: ${id}`);
}, []);
return (
<>
{posts.map((post) => (
<div key={post._id}>
<h3 className="title" onClick={toggleModal(post._id)}>
{post.title}
</h3>
<div">
<a>
<span onClick={toggleModal(post._id)}>Read More</span>
</a>
</div>
// Clicking on either span or a tag shows the Modal
<Modal
isOpen={isOpen}
onRequestClose={toggleModal(post._id)}>
// Closes modal
<button className="close-modal" onClick={toggleModal(post._id)}>
<img
src="assets/img/svg/cancel.svg"
alt="close icon"/>
</button>
// Want to show content based on _id
<h3 className="title">{post.title}</h3>
<p className="body">{post.body}</p>
</div>
)
</>
)
}

Close menu when clicking outside the React component

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>
);
}

Hiding Sidebar Component on Outside Click

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.

How to make clicking an anchor equivalent to clicking a details tag?

I have a <details> tag, on click of it toggles some content. Now I have an <a> tag underneath it, on click of the <a> tag I'd like to toggle the same content, i.e. clicking the <a> should be equivalent to clicking the <details>. Here is a code snippet I've tried:
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Menu = ({ toggleDetails }) => {
return (
<div>
<a href="/#" onClick={toggleDetails}>
Open
</a>
</div>
);
};
const Details = (isOpen) => {
return (
<details>
<summary>Hello</summary>
{isOpen ? <div>Hi</div> : null}
</details>
);
};
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleDetails = () => {
setIsOpen(isOpen ? false : true);
};
return (
<div>
<Details isOpen={isOpen} />
<Menu toggleDetails={toggleDetails} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
Here on click of 'Hello', it toggles 'Hi'. I'd like to do the same thing on click of 'Open', i.e. toggles 'Hi'. How can I do it? The conditional rendering does not work. Should I use a ref to access the 'open' property of the <details> tag?
EDIT:
I also tried the ref solution as follows but it didn't work:
const Details = (isOpen) => {
const detailsRef = useRef();
// detailsRef.current.open = isOpen
return (
<details ref={detailsRef}>
<summary>Hello</summary>
<div>Hi</div>
</details>
);
};
I assume you are trying to use the details tag's native toggle functionality. In order to do that, you need to control the open/closed state via the open attribute. You should then use the onToggle event to detect when the summary element is clicked, so you can keep your component's state in sync with the actual DOM.
const Menu = ({ setIsOpen }) => {
return (
<div>
<a
href="#"
onClick={() => {
setIsOpen((prev) => !prev);
}}
>
Open
</a>
</div>
);
};
const Details = ({ isOpen, setIsOpen }) => {
return (
<details
open={isOpen}
onToggle={(event) => {
setIsOpen(event.target.open);
}}
>
<summary>Hello</summary>
<div>Hi</div>
</details>
);
};
const App = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Details isOpen={isOpen} setIsOpen={setIsOpen} />
<Menu setIsOpen={setIsOpen} />
</div>
);
};
You need to transfer information between both components to do that you either needs to:
1: State penetration,
2: Redux.
You are attempting to change a component that is not connected to the one you are calling. The Hi div is on the Details component which is not in direct relationship with the Menu component. Now regarding your specific problem, you can do it by pushing the state on a higher component which in this case is App.js.
Now I do not understand if you are trying to make the app work in this way as a coding challenge or if you do not know better. If it is the latter please reply in the comments so I can provide a direct solution.
Pretty sure all you need to do is ensure that details open attribute is set to true or false depending on if you want it open or.not.
<details open={isOpen}>...</details>

React Redux: Different actions for list items

I have a list consisting of React components:
<div className="card-list">
{cards.map((card, i) => (
<Card card={card} key={card.cardId} index={i}/>
))}
</div>
The problem is that clicking on a card should open a modal window for the card that was clicked. To call the modal window I use the following code inside the Card component:
const Card = ({card, key, index}) => {
const dispatch = useDispatch();
const showModal = useSelector(state => state.modal.showContentModal);
return (
<div>
//this is card view
<div onClick={() => dispatch(showContentModal())}>
<h3>{card.name}</h3>
</div>
//this is modal window
<div id={`modal-overlay-container-${key}`} className={`modal-overlay ${showModal && "active"}`}>
<div id={`modal-div-${key}`} className={`modal ${showModal && "active"}`}>
<p className="close-modal" onClick={() => dispatch(hideContentModal())}>
<svg viewBox="0 0 20 20">
<path fill="#8e54e9" d="..."/>
</svg>
</p>
<div className="modal-content">
<CardContent card={card} index={index}/>
</div>
</div>
</div>
</div>
)
}
export default Card;
The showСontentModal() action changes the Boolean flag, which makes the modal window active, and hideModalContent() does the opposite. But because all the components in the list are linked to a single action, clicking on any card opens a modal window for all of them. Is there any way to trigger an action only for a specific card?
I see two ways you could solve this.
Use local state inside card component instead of redux state
Use card id instead of boolean in state.modal.showContentModal
If you don't need state.modal.showContentModal in other components then I would prefer first option.
const [showModal, setShowModal] = useState(false)
const handleOpenModal = () => setShowModal(true)
<div onClick={handleOpenModal}>
<h3>{card.name}</h3>
</div>

Categories

Resources