GatsbyJS Burger Menu close menu on AnchorLink click - reactjs

I have created a working burger menu in my Gatsby application using react-burger-menu. I am trying to get it so that when an AnchorLink in the burger is clicked, the Burger menu closes. I have tried doing <Menu isOpen={ false }> as suggested in the docs but not working. Any help?
This is my Burger menu code:
import React from "react"
import { slide as Menu } from "react-burger-menu"
import { AnchorLink } from "gatsby-plugin-anchor-links"
const Burger = () => {
return (
<div className="burger-menu">
<Menu right isOpen={ false }>
<AnchorLink to="/#about">About Me</AnchorLink>
<AnchorLink to="/#experience">Experience</AnchorLink>
<AnchorLink to="/#projects">Projects</AnchorLink>
<AnchorLink to="/#contact">Contact</AnchorLink>
</Menu>
</div>
)
}
export default Burger

You can provide event handlers for Menu and AnchorLink and add a state menuOpen to close/open sidebar.
const Burger = () => {
const [menuOpen, setMenuOpen] = React.useState(false)
// This keeps your state in sync with the opening/closing of the menu
// via the default means, e.g. clicking the X, pressing the ESC key etc.
const handleStateChange = state => {
setMenuOpen(state.isOpen)
}
// This can be used to close the menu, e.g. when a user clicks a menu item
const closeMenu = () => {
setMenuOpen(false)
}
return (
<div className="burger-menu">
<Menu
right
isOpen={menuOpen}
onStateChange={state => handleStateChange(state)}
>
<AnchorLink
to="/#about"
onClick={() => {
closeMenu()
}}
>
About Me
</AnchorLink>
<AnchorLink
to="/#experience"
onClick={() => {
closeMenu()
}}
>
Experience
</AnchorLink>
<AnchorLink
to="/#projects"
onClick={() => {
closeMenu()
}}
>
Projects
</AnchorLink>
<AnchorLink
to="/#contact"
onClick={() => {
closeMenu()
}}
>
Contact
</AnchorLink>
</Menu>
</div>
)
}

Related

Shared Popper requires two clicks to reopen

I've got buttons that represent sports. When a user clicks on a button a Popper is opened with the sport's associated component/content. The same Popper is shared between all the buttons.
This works fine, but once the Popper is open it requires two clicks to open the Popper for another sport. One click to close the Popper and the second click to open it. So if I click the 'Baseball' button, I'll have to click the 'Basketball' button twice before the Popper is reopened with the Basketball content.
Is there any way to accomplish this with just a single click? Link to Sandbox
import React, { useEffect, useState } from 'react';
import { Popper, Button, Paper, Typography } from "#material-ui/core";
function CategoryMenuItemContent(props) {
return (
<>
<p>{props.menu.label}</p>
<p>{props.menu.content}</p>
</>
);
}
export default function App() {
const baseballCategory = {
label: 'Baseball',
content: <p>some content</p>
};
const basketballCategory = {
label: 'Basketball',
content: <p>some content</p>
};
const footballCategory = {
label: 'Football',
content: <p>some content</p>
};
const hockeyCategory = {
label: 'Hockey',
content: <p>some content</p>
};
const [activeCategoryMenuContent, setActiveCategoryMenuContent] = React.useState('baseball');
const categoryMenuItemContentComponents = {
"baseball": <CategoryMenuItemContent menu={baseballCategory} />,
"basketball": <CategoryMenuItemContent menu={basketballCategory} />,
"football": <CategoryMenuItemContent menu={footballCategory} />,
"hockey": <CategoryMenuItemContent menu={hockeyCategory} />,
};
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
setActiveCategoryMenuContent(event.currentTarget.textContent.toLowerCase());
};
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : undefined;
return (
<>
<Button onClick={(event) => {handleClick(event);}}>Baseball</Button>
<Button onClick={(event) => {handleClick(event);}}>Basketball</Button>
<Button onClick={(event) => {handleClick(event);}}>Football</Button>
<Button onClick={(event) => {handleClick(event);}}>Hockey</Button>
<Popper id={id} open={open} anchorEl={anchorEl}>
{activeCategoryMenuContent === 'baseball' && categoryMenuItemContentComponents['baseball']}
{activeCategoryMenuContent === 'basketball' && categoryMenuItemContentComponents['basketball']}
{activeCategoryMenuContent === 'football' && categoryMenuItemContentComponents['football']}
{activeCategoryMenuContent === 'hockey' && categoryMenuItemContentComponents['hockey']}
</Popper>
</>
);
}
This would make more sense:
const handleClick = (event) => {
if (anchorEl === event.currentTarget) {
setAnchorEl(null)
setActiveCategoryMenuContent('')
} else {
setAnchorEl(event.currentTarget);
setActiveCategoryMenuContent(event.currentTarget.textContent.toLowerCase())
}
};

React: How to prevent menu component from being remounted when switching pages

Let's say that we have a React app with two pages A and B using a shared menu component Menu.
Our app renders either page A or page B, like the example below:
const Menu = (props) => {
React.useEffect(()=>{
console.log("The menu remounted");
}, []);
return (
<div id="menu" className="has-scrollbar">
<button onClick={() => props.onClick('a')}>A</button>
<button onClick={() => props.onClick('b')}>B</button>
</div>
);
}
const PageA = (props) => {
const .. = useSomeHooksUsedByPageA();
return (
<div>
<Menu {...somePropsFromPageA} />
<div>Content of page A</div>
</div>
);
}
const PageB = (props) => (
const .. = useSomeHooksUsedByPageB();
<div>
<Menu {...somePropsFromPageB} />
<div>Content of page B</div>
</div>
);
const App = () => {
const [pageKey, setPageKey] = React.useState("a");
switch (pageKey)
{
case "a":
return <PageA key="1" onClick={setPageKey} />;
case "b":
return <PageB key="1" onClick={setPageKey} />;
}
return "true"
}
Now, every time we switch pages (from A to B, or B to A), the menu is remounted and a message is printed to the console.
Using this component hierarchy where the menu receives props from the page, is there any way to tell React not to remount the menu when we switch pages?
(A typical use-case could be that the menu has a scroll, and we want to keep the scroll position when navigating different pages.)
Help is greatly appreciated!
One potential solution for this problem is to move <Menu/> into the <App/> component, and render each page after the menu.
This provides a couple of benefits:
The Menu won't be re-rendered whenever the page changes.
The onClick function does not need to be passed through props on each page just to provide it to the <Menu/> component nested within.
const Menu = (props) => {
React.useEffect(() => {
console.log("The menu remounted");
}, []);
return (
<div id="menu" className="has-scrollbar">
<button onClick={() => props.onClick("a")}>A</button>
<button onClick={() => props.onClick("b")}>B</button>
</div>
);
};
const PageA = () => (
<div>
<div>Content of page A</div>
</div>
);
const PageB = () => (
<div>
<div>Content of page B</div>
</div>
);
const App = () => {
const [pageKey, setPageKey] = React.useState("a");
let page;
switch (pageKey) {
case "b":
page = <PageB key="2" />;
break;
default:
page = <PageA key="3" />;
break;
}
return (
<>
<Menu onClick={setPageKey} />
{page}
</>
);
};
ReactDOM.render(<App />, document.querySelector("#app"))
Edit
Further to #glingt's comment regarding the hierarchy and how this needs to function, Context might be a good candidate for the use case. If pages need to update the <Menu/> component's props, then using context to manage state between the menu and pages might be a better solution in terms of architecture. Instead of rendering many <Menu/> components inside of each child, only one <Menu/> can be rendered higher up in the tree. This results in the component mounting once rather than many times with each child. Effectively, context manages the state of the menu, and provides methods to update state to any children under the provider. In this case, both child pages and the menu can update and respond to state updates.
import "./styles.css";
import React, { useContext, useMemo, useState } from "react";
// Create an instance of context so we are able to update the menu from lower in the tree
const menuContext = React.createContext({});
// Add state to the context provider. Wrap props earlier in the tree with this component.
const MenuContext = ({ children }) => {
const [pageKey, setPageKey] = useState("a");
const value = useMemo(() => ({ pageKey, setPageKey }), [pageKey]);
return <menuContext.Provider value={value}>{children}</menuContext.Provider>;
};
// The menu component which will:
// 1. Update the menuContext when the user selects a new pageKey
// 2. Respond to updates made to the pageKey by other components (in this case pages)
const Menu = () => {
const { pageKey, setPageKey } = useContext(menuContext);
React.useEffect(() => {
console.log("The menu remounted");
}, []);
return (
<div id="menu" className="has-scrollbar">
<button
onClick={() => setPageKey("a")}
style={{ color: pageKey === "a" ? "blue" : "red" }}
>
A
</button>
<button
onClick={() => setPageKey("b")}
style={{ color: pageKey === "b" ? "blue" : "red" }}
>
B
</button>
</div>
);
};
// In each page, we are able to update a value that is consumed by the menu using setPageKey
const PageA = () => {
const { setPageKey } = useContext(menuContext);
return (
<div>
<div>Content of page A</div>
<button onClick={() => setPageKey("b")}>Go to page B</button>
</div>
);
};
const PageB = () => {
const { setPageKey } = useContext(menuContext);
return (
<div>
<div>Content of page B</div>
<button onClick={() => setPageKey("a")}>Go to page A</button>
</div>
);
};
const PageComponent = () => {
const { pageKey } = useContext(menuContext);
switch (pageKey) {
case "b":
return <PageB key="2" />;
default:
return <PageA key="1" />;
}
};
const App = () => (
<MenuContext>
<Menu />
<PageComponent />
</MenuContext>
);
ReactDOM.render(<App />, document.querySelector("#app"))

how to stop InView in react-intersection-observer

I use inView in my component. Until the posts were loaded, the user see the skeleton. But when I press delete button, the skeleton appears and dissappers. Can I stop this quick appearance when press the delete button.
class MyPosts extends Component {
deleteItem = (key) => {
this.props.deleteItem(key);
};
render() {
const { myPosts, classes } = this.props;
let posts = myPosts.map((item) => {
return (
<InView threshold={0}>
{({ ref, inView }) => (
<div ref={ref} inView={inView}>
{inView ? (
<Card>
<Typography >
{item.post}
</Typography>
<Button
onClick={() => this.deleteItem(item.key)}
>
Done
</Button>
</Card>
) : (
<Skeleton />
)}
</div>
)}
</InView>
);
});
return <div>{posts}</div>;
}
}

How can I put a button into collapse?

I want to put a button into a collapse, I am using the collapse of antd, that new button doesn't should open or close the collapse, I want to give her other functionality?
const { Collapse, Button } = antd;
const { Panel } = Collapse;
function callback(key) {
console.log(key);
}
const text = ` hi `;
ReactDOM.render(
<Collapse
defaultActiveKey={['1']} onChange={callback}>
<Panel header={<Button type="primary">Primary Button</Button>} key="1" >
<Button type="link">My button</Button> >
<p>{text}</p>
</Panel>
</Collapse>,
mountNode,
);
Why does the COLLAPSE open when I click the button? I don't want the COLLAPSE to open
I guess the button you want to not open the collapse is your Primary Button in header of Panel, To do that you have to control activeKey manually, and check that when user click on the Panel header are they click on the Primary Button or outside of it.
Try this:
import React, { useState, useRef } from "react";
import * as antd from "antd";
const { Collapse, Button } = antd;
const { Panel } = Collapse;
const text = ` hi `;
export const App = () => {
const [activeKey, setActiveKey] = useState(0);
const isButtonClicked = useRef(false);
const callback = key => {
console.log(key, isButtonClicked.current);
// Check if use click on the button don not update activekey
if (
isButtonClicked &&
isButtonClicked.current &&
isButtonClicked.current === true
) {
isButtonClicked.current = false;
return;
}
if (!activeKey) {
setActiveKey(key);
} else {
setActiveKey(0);
}
};
return (
<Collapse activeKey={activeKey} onChange={callback}>
<Panel
header={
<Button
onClick={() => {
// set the isButtonClicked ref to tru when user click on Button
isButtonClicked.current = true;
// Do other functionality you want for this button here
}}
type="primary"
>
Primary Button
</Button>
}
key="1"
>
<Button type="link">My button</Button> ><p>{text}</p>
</Panel>
</Collapse>
);
};
I create a codesandbox to demonstrate it
try this:
const { Collapse, Button } = antd;
const { Panel } = Collapse;
function callback(key) {
console.log(key);
}
const text = ` hi `;
ReactDOM.render(
<Collapse defaultActiveKey={['1']} onChange={callback}>
<Panel header={<Button type="primary">Primary Button</Button>} key="1" >
<Button type="link">My button</Button>
<p>{text}</p>
</Panel>
</Collapse>,
mountNode,
);
If you want a button inside a panel, the button code should be inside the panel, not inside its tag.. instead of:
<Panel header="This is panel header 1" key="1"
<Button type="link">My button</Button> >
<p>{text}</p>
</Panel>
this should be
<Panel header="This is panel header 1" key="1">
<Button type="link">My button</Button>
<p>{text}</p>
</Panel>

Material-ui: open menu by event hover

Currently the menuItem only opens your children after a click. Is there an attribute that I agree to open via Hover?
<MenuItem key={index}
menuItems={menuitems}
**onHover={true}**
>
menuItem
</MenuItem>
There is not specific attribute available through the material-ui library. However, you could create this yourself pretty easily using the onMouseOver event.
I've adapted the Simple Menu example from the material-ui site to show you how this can be done:
import React from 'react';
import Button from 'material-ui/Button';
import Menu, { MenuItem } from 'material-ui/Menu';
class SimpleMenu extends React.Component {
state = {
anchorEl: null,
open: false,
};
handleClick = event => {
this.setState({ open: true, anchorEl: event.currentTarget });
};
handleRequestClose = () => {
this.setState({ open: false });
};
render() {
return (
<div>
<Button
aria-owns={this.state.open ? 'simple-menu' : null}
aria-haspopup="true"
onClick={this.handleClick}
{ // The following line makes the menu open whenever the mouse passes over the menu }
onMouseOver={this.handleClick}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={this.state.anchorEl}
open={this.state.open}
onRequestClose={this.handleRequestClose}
>
<MenuItem onClick={this.handleRequestClose}>Profile</MenuItem>
<MenuItem onClick={this.handleRequestClose}>My account</MenuItem>
<MenuItem onClick={this.handleRequestClose}>Logout</MenuItem>
</Menu>
</div>
);
}
}
export default SimpleMenu;
I got it to work by upping the z-index of the button. Otherwise, the mouse technically goes out of the button when the modal appears on top of the button. Then the menu will close since the user is no longer hovering.
If you add onMouseLeave to Menu then onMouseLeave will only trigger if you go out of the browser. So instead, I added onMouseLeave to MuiList which doesn't take up the whole page.
I also added need some extra conditionals in the handleOpen to account for if the mouse leaves the button but enters the menu.
import React, { useState } from "react";
import Button from "#material-ui/core/Button";
import Menu from "#material-ui/core/Menu";
import { createMuiTheme, ThemeProvider } from "#material-ui/core/styles";
const theme = createMuiTheme({});
const MyMenu = () => {
const [anchorEl, setAnchorEl] = useState(null);
const [open, setOpen] = useState(false);
const handleOpen = (event) => {
setAnchorEl(event.currentTarget);
setOpen(true);
};
const handleClose = (e) => {
if (e.currentTarget.localName !== "ul") {
const menu = document.getElementById("simple-menu").children[2];
const menuBoundary = {
left: menu.offsetLeft,
top: e.currentTarget.offsetTop + e.currentTarget.offsetHeight,
right: menu.offsetLeft + menu.offsetWidth,
bottom: menu.offsetTop + menu.offsetHeight
};
if (
e.clientX >= menuBoundary.left &&
e.clientX <= menuBoundary.right &&
e.clientY <= menuBoundary.bottom &&
e.clientY >= menuBoundary.top
) {
return;
}
}
setOpen(false);
};
theme.props = {
MuiList: {
onMouseLeave: (e) => {
handleClose(e);
}
}
};
return (
<div>
<ThemeProvider theme={theme}>
<Button
id="menubutton1"
aria-owns={open ? "simple-menu" : null}
aria-haspopup="true"
onMouseOver={handleOpen}
onMouseLeave={handleClose}
style={{ zIndex: 1301 }}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={open}
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
transformOrigin={{
vertical: "top",
horizontal: "center"
}}
>
Menu
<br />
Items
</Menu>
</ThemeProvider>
</div>
);
};
export default MyMenu;
CodeSandbox
I have added mouseLeave listener on container div to close the menu, and mouseOver listener on menu button to open menu. This worked for me...
<div onMouseLeave={closeMenu}>
<button onMouseOver=(openMenu) />
<Menu />
<MenuItems />
</div>
This is how I did it:
https://codesandbox.io/s/mui-menu-hover-to-show-dropdown-iguukw?file=/src/TopMenu.tsx
I used on onMouseLeave and onMouseEnter events to control when to show and hide the dropdown menus.
I also used a string state to determine which dropdown menu should show. Only one dropdown menu should show at one moment of time.

Resources