22
loading...
This website collects cookies to deliver better user experience
// track clicks that should close the menu, but don't
const [clickEvent, setClickEvent, clickEventRef] = useState(false);
// state to track whether the menu is open or not
const [menuIsOpen, setMenuIsOpen] = useState(false);
// define a context object with the states you need
const context = {
clickEvent,
setClickEvent,
clickEventRef,
menuIsOpen,
setMenuIsOpen,
};
clickEvent
state will be used like a toggle flip-flop.// in a file called context.js, create context object with createContext hook.
import { createContext } from "react";
export const MenuContext = createContext(null);
/* import that context object where you will be wrapping your routes
in the context. here's what mine looks like in that file.
*/
import { MenuContext } from "../util/context.js";
/* now you will wrap the routes in the context provider. make sure
the context definition containing your states is defined in
the same file as your routes
*/
const context = {
clickEvent,
setClickEvent,
clickEventRef,
menuIsOpen,
setMenuIsOpen,
};
return (
<MenuContext.provider value={context}>
<Header />
<Routes />
<Footer />
</MenuContext.provider>
);
// your import statements may look different but here's mine
import React, {useEffect, useContext, useRef, useState} from "react";
import { MenuContext } from "../utils/context";
export default function Header() {
// "tell" react you want the context in the MenuContext.provider
const context = useContext(MenuContext)
...
}
menuIsOpen
state is supposed to track whether the menu is open or not, I put this functionality in.<Navbar.Toggle
onClick={() => context.setMenuIsOpen(() => !context.menuIsOpen)}
...
/>
useRef
came in handy.const toggleHamburger = useRef();
// in the return statement, same toggle from earlier
<Navbar.Toggle ref={toggleHamburger} ... />
// here is the custom hook I built in a file simply named useSideClick.js
export default function useSideClick({
clickEvent,
setClickEvent,
clickEventRef,
setMenuIsOpen,
}) {
return function () {
setClickEvent(() => !clickEvent);
setMenuIsOpen(() => clickEventRef);
};
}
clickEventRef
makes sure that we have the most current state of clickEvent
after a recent change. It is necessary because there is always a possibility that a change in state will take too long to be referenced immediately afterward thanks to state changes being a little bit asynchronous.// in Header
import React, { useEffect, useContext, useRef, useState } from "react";
import { MenuContext } from "../utils/context";
import useSideClick from "../util/useSideClick";
export default function Header() {
const context = useContext(MenuContext);
const handleSideClick = useSideClick(context);
...
}
clickEvent
and menuIsOpen
states. We have grabbed an instance of that function. Now we have to call that function upon clicks on the nav links and have a useEffect that reacts to a change in clickEvent
's state.export default function Header() {
// handleSideClick changes clickEvent, so the useEffect will catch this change.
useEffect(handleClickSideEffect, [context.clickEvent]);
function handleClickSideEffect() {
// we don't want it to toggle if it is already closed
if (context.menuIsOpen) {
// click the toggle button using current toggleHamburger ref we made earlier
toggleHamburger.current.click();
/* reflect the change to the menu in its state. we can be explicit
and set it to false since we know we are closing it.
*/
context.setMenuIsOpen(() => false);
}
}
return (
...
<Nav.Link onClick={handleSideClick}>Home</Nav.Link>
...
);
}