'How to execute JS when a Gatsby <Link> Component is clicked
I'm using JS to add CSS classes that prevent scrolling on my site when my mobile hamburger menu is open.
However, using Gatsby's <Link> components for internal links, when a new internal page is clicked, the "no-scroll" classes remain in place with the mobile menu closed... The end result is the new page being rendered, but with the scroll locked and the hamburger menu, inaccessible.
I'd like to be able to execute JS when a Gatsby <Link> component is clicked so I can remove the "no-scroll" class from the appropriate elements.
Here's my JS.
(function(){
var burger = document.querySelector('#burger_container'),
body = document.querySelector("body"),
html = document.querySelector('html'),
navMobile = document.querySelector('#nav_mobile'),
nav = document.querySelector('#nav');
burger.onclick = function() {
navMobile.classList.toggle('menu_opened');
nav.classList.toggle('menu_opened');
body.classList.toggle('no-scroll');
html.classList.toggle('no-scroll');
}
}());
Solution 1:[1]
I extremely discourage you from using your snippet. With this:
(function(){
var burger = document.querySelector('#burger_container'),
body = document.querySelector("body"),
html = document.querySelector('html'),
navMobile = document.querySelector('#nav_mobile'),
nav = document.querySelector('#nav');
burger.onclick = function() {
navMobile.classList.toggle('menu_opened');
nav.classList.toggle('menu_opened');
body.classList.toggle('no-scroll');
html.classList.toggle('no-scroll');
}
}());
You are pointing directly to the DOM elements (because of the document.querySelector), in this case, looking for #burger_container, body, html, #nav_mobile and #nav elements. On the other hand, React, hence Gatsby, deals and manipulates the virtual DOM (vDOM). Playing and manipulating both will cause further potential issues like one not knowing when the other changes and vice-versa, which, in a real scenario is translated to elements not being rehydrated when you expect: classes that are not being removed when you want, elements not displayed properly (not hiding/not showing), etc. You can achieve the same result using the useRef hook along with the .current option in a React-based approach. You can still use it, but at your own risk and having that in mind.
That said, I do not agree with @Lysmask answer. onClick can be used in a Link component of the course, but it will be ignored prior to the navigation because you are not locking or preventing that behavior, so your onClick function will be ignored. In the end, the Link component is translated as an anchor (<a>).
Given that scenario, what you can do is to "fake" the Link component using a custom React component that triggers your function, and then navigates to the desired page:
const fakeNavigation=(url)=>{
//do your function here
navigate(url)
}
<div onClick={()=>fakeNavigation('/contact')}>Some fake link</div>
navigate works in the same way as Link but can be used in programmatic in-app navigation (check https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-link/#how-to-use-the-navigate-helper-function for more details)
Another option is using one of the multiple gatsby-browser.js APIs. In this case, onRouteUpdate. This function will be fired in each route change, including the initial load of the app. You can easily use your function there to remove the no-scroll class. It also exposes the previous location (prevLocation) and the new location (location) props:
exports.onRouteUpdate = ({ location, prevLocation }) => {
console.log('new pathname', location.pathname)
console.log('old pathname', prevLocation ? prevLocation.pathname : null)
//do your function here
}
Solution 2:[2]
As any other element, You can add the onClick attribute to the Link element itself. Like this ->
<Link onClick={ () => myfunction }> </Link>
Otherwise, you can maybe have a useEffect hook which runs everytime a component re-renders. With this - you have a true/false state for your hamburger menu is currently open, and depending on that, u toggle your classes.
Something like.
const [openHamburgerMenu, setOpenHamburgerMenu] = useState(false)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Ferran Buireu |
