1

I need help with my code. The thing I want create is to change className according to page url
So when I scroll or go to page /kontakt I want to change class from "hamburger" to "hamburger active". I also tried regex.
Any ideas?
Here is code:

const HamMenu = ()=> {
    const [sidebar, setSidebar] = useState(false)
    const [burger, setBurger] = useState(false)
    const url = window.location.href;

    const showSidebar = () => setSidebar(!sidebar)
    const changeColor = () => {
        if((window.scrollY >= 60) || (url.indexOf("kontakt") > -1)){
            setBurger(true);
        } else {
            setBurger(false);
        }
    }

    window.addEventListener('scroll', changeColor);

    return (
        <StyledMenu>

            <div>
                <Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars'} >
                    <FontAwesomeIcon 
                        icon={faBars} 
                        size="2x" 
                        className={burger ? 'hamburger active' : 'hamburger'} 
                        onClick={showSidebar}
                    />
                </Link>
            </div>
Wookie
  • 21
  • 1
  • 3
  • I don't think this is why your code "isn't working" (we probably need more details for that - *what* isn't working?), but it's a really bad idea to use `window.addEventListener` inside the main component body. This will result in a new event listener being added every time the component rerenders for any reason! You should add this inside a `useEffect` call, and return a function that uses `removeEventListener` to clean this up when your component unmounts. – Robin Zigmond Feb 08 '21 at 17:21
  • change color on scroll is working but when I go to page "/kontakt" nothing happen. This is of course component named HamMenu. Thanks for tip with useEffect – Wookie Feb 08 '21 at 17:28
  • I'm not sure what the problem is there - but basic things first, have you tried stepping through your code? Is it getting to the `setBurger(true)` call? If not, check what `url` is. If is is, check the class in your DOM and make sure your CSS is correct to have it the colour you want. Etc... – Robin Zigmond Feb 08 '21 at 17:34

2 Answers2

1

Dealing with window in Gatsby could be a little bit tricky because two fundamental reasons:

  • window object is only defined in the browser, so it will work perfectly under gatsby develop but you will need to add a "hack" to avoid a code-breaking in the gatsby build (because there's no window in the Node server).
  • Treating the window outside React ecosystem, may break the rehydration of the components. This means that React won't potentially know what components need to re-render on-demand, causing unmounted components, especially when navigating forward and backward using the browser's history.

There are a few workarounds to achieve what you're trying to do.

Gatsby, by default, provides a location prop in all top-level components (pages). So you can pass it to any child components at any time to change the class name based on its value:

const IndexPage = ({ location }) =>{

return <Layout>
   <HamMenu location={location} />
   <h1> some content</h1>
</Layout>
}

Then, in your <HamMenu> component:

const HamMenu = ({ location })=> {
    const [sidebar, setSidebar] = useState(false)
    const [burger, setBurger] = useState(false)
    const url = window.location.href;

    const showSidebar = () => setSidebar(!sidebar)
    const changeColor = () => {
        if((window.scrollY >= 60) || (url.indexOf("kontakt") > -1)){
            setBurger(true);
        } else {
            setBurger(false);
        }
    }

  useEffect(() => {
    if(typeof window !== "undefined"){
    const url = window.location.href

    const changeColor = () => {
      setBurger(window.scrollY >= 60 || url.contains("kontakt"))
    }

    window.addEventListener('scroll', changeColor)

   return () => {
     window.removeEventListener('scroll', changeColor)
    }
   }
  }, [])


    return (
        <StyledMenu>

            <div>
                <Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars' location.pathname.includes("your-page")? ''some-class' : 'some-other-class' } >
                    <FontAwesomeIcon 
                        icon={faBars} 
                        size="2x" 
                        className={burger ? 'hamburger active' : 'hamburger'} 
                        onClick={showSidebar}
                    />
                </Link>
            </div>

I would suggest another approach to get the scroll position rather than using directly the window, using React-based approach to avoid what I was pointing before (How to add a scroll event to a header in Gatsby).

However, I've fixed your initial approach, wrapping it inside a useEffect with empty deps ([]). This function will be triggered once the DOM tree is loaded, to avoid the code-breaking window use that I was talking about. Alternatively to url.indexOf("kontakt") > -1 you may want to use url.includes("kontakt") which is way more readable.

Regarding the rest, it's quite self-explanatory. Destructuring the location props you get access to a bunch of data, the pathname property holds the page name so based on that, you can add a ternary condition wherever you want, such as location.pathname.includes("your-page") ? ''some-class' : 'some-other-class' (includes is more semantic in my opinion).

As you see, I've fixed your approach but I've also added a React/Gatsby-based one, choose what makes you feel comfortable.

Ferran Buireu
  • 28,630
  • 6
  • 39
  • 67
0

React components rendering server-side (such as during gatsby build) do not have access to window, and in order to avoid breaking hydration, the first render needs to match what is rendered server-side. For these reasons, you'll want to use useEffect to make client-side changes that rely on window after the component mounts.

Note that this solution is going to perform rather poorly since changeColor is calling setBurger on each scroll event, which prompts the component to be re-rendered (even if the value is the same). You'll want to add a debounce or throttle routine to mitigate this.

const HamMenu = ()=> {
  const [burger, setBurger] = useState(false)

  useEffect(() => {
    const url = window.location.href

    const changeColor = () => {
      setBurger(window.scrollY >= 60 || url.contains("kontakt"))
    }

    window.addEventListener('scroll', changeColor)

    return () => {
      window.removeEventListener('scroll', changeColor)
    }
  }, [])

  return (
    <StyledMenu>
      <div>
        <Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars'} > 
          <FontAwesomeIcon
            icon={faBars}
            size="2x" 
            className={burger ? 'hamburger active' : 'hamburger'} 
          />
        </Link>
      </div>
    </StyledMenu>
  )
}
coreyward
  • 77,547
  • 20
  • 137
  • 166