Search code examples
javascriptcssreactjsgatsbystyled-components

Custom Navbar in Gatsbyjs


I am having an issue with a custom navbar. I ain't going to lie, I did copy it but I wanted to make it better. The issue specifically I am having is on the mobile side. When I click a link in the navbar, the navbar menu doesn't disappear. It stays out and the page loads to the clicked link. How can I use useEffect or State or props to make the navbar slide out of the view?

By the way, tailwindCss and bootstrap are out of the question for me because I wanted to use styled components and get my css skills up too.

I appreciate any feedback.

edited code:

import React, { useState, useEffect, useRef } from "react";
import styled from "styled-components";
import { Link } from "gatsby";

const Navigation = styled.nav`
  display: flex;
  position: sticky;
  position: -webkit-sticky;
  position: -moz-sticky;
  position: -ms-sticky;
  position: -o-sticky;
  background-color: var(--white);
  justify-content: space-between;
  text-transform: uppercase;
  border-bottom: 2px solid var(--light-green-shadow);
  width: 100%;
  margin: 0;
  top: 0;
  padding: 0.5rem 0;
  z-index: 2;
  align-items: center;
  box-sizing: border-box;
  transition: background ease-out 300ms;
  background: ${({ background }) => background ? `var(--light-green-tint)` : `var(--light-green)`};

  @media (max-width: 768px) {
    height: 5vh;
    position: sticky;
    top: 0;
    left: 0;
    right: 0;
    left: 0;
  }
`

const Toggle = styled.div`
  display: none;
  height: 100%;
  cursor: pointer;
  padding: 0 10vw;

  @media (max-width: 768px) {
    display: flex;
  }
`

const Navbox = styled.div`
  display: flex;
  height: 100%;
  justify-content: flex-end;
  align-items: center;
  box-sizing: border-box;
  
  ul {
        display: flex;
        align-items: center;
        width: 100;
        justify-content: end;
        gap: 2rem;
        padding: 0 1rem;
        flex-direction: row;
        li {
            list-style: none;
            transition: transform 500ms;
            :hover {
                transform: translateY(-10px);

            }
            a {
                text-decoration: none;
                transition: color 300ms, transform 500ms;
                color: var(--white);
                :hover {
                    color: var(--light-grey);
                } 
            }
        }
    }

  @media (max-width: 768px) {
    flex-direction: column;
    position: fixed;
    width: 100%;
    justify-content: flex-start;
    padding-top: 10vh;
    background-color: var(--black);
    transition: all 0.3s ease-in;
    top: 6.5vh;
    right: ${({open}) => open ? "0" : "-100%"};
    ul {
        flex-direction: column;
    }
  }
`

const LogoStyle = styled.div`
    margin-left: 2.5rem;
    a {
        text-decoration: none;
        white-space: nowrap; 
        font-size: 1.65rem;
        @media (min-width: 768px) {
            white-space: nowrap;
        }
    }
`;

const Hamburger = styled.div`
  background-color: #111;
  width: 30px;
  height: 3px;
  transition: all .3s linear;
  align-self: center;
  position: relative;
  transform: ${({open}) => open ? "rotate(-45deg)" : "inherit"};

  ::before,
  ::after {
    width: 30px;
    height: 3px;
    background-color: #111;
    content: "";
    position: absolute;
    transition: all 0.3s linear;
  }

  ::before {
    transform: ${({open}) => open ? "rotate(-90deg) translate(-10px, 0px)" : "rotate(0deg)"};
    top: -10px;
  }

  ::after {
    opacity: ${({open}) => open ? "0" : "1"};
    transform: ${({open}) => open ? "rotate(90deg) " : "rotate(0deg)"};
    top: 10px;
  }
`
const Navbar = () => {
  const [navbarOpen, setNavbarOpen] = useState(false);

  const [background, setBackground] = useState(false);
    

  const changeBackground = () => {
      if (window.scrollY > 25) {
          setBackground(true);
      } else {
          setBackground(false);
      }
  }

  const toggleNavbar = () => setNavbarOpen(open => !open);

  useEffect(() => {


      window.addEventListener('scroll', changeBackground);
      console.log(background);
      return () => {
          window.removeEventListener('scroll', changeBackground)
      }
  },[background]);


  return (
    <Navigation background={background}>
       <LogoStyle> 
          <Link to="/">Performant Web Design</Link>
        </LogoStyle>
      <Toggle
        onClick={ toggleNavbar()}
      >
      <Hamburger open={navbarOpen} />
      </Toggle>
      <Navbox open={navbarOpen}>
        <ul>
            <li>
              <Link to='/services'>Services</Link>
            </li>
            <li>
                <Link to="/about">About</Link>
            </li>
            <li>
                <Link to="/contact">Contact</Link>
            </li>
        </ul>
      </Navbox>
    </Navigation>
  )
}

export default Navbar

Solution

  • By default, when navigating to another page the state should be reset. The onClick in the Link won't get any effect because it's not a prop accepted by the component (it's not your components, it's Gatsby's). Try debugging there because all the solutions dirty the code, because it should be the default behavior.

    What you can do in the meantime, is to intercept that click in your <li>

    const handleClick=(to)=>{
       setNavbarOpen(false);
       navigate(to);
    }
    
    <li onClick={handleClick('/services')}>
        <span>Services</span>
    </li>
    

    Note: this should be a temporary solution while you find the cause of the unsettled state


    Taking your words of making it better, you can refactor this:

      const closeNav = () => {
        if (navbarOpen) {
          setNavbarOpen(false)
        }
      }
    
      const openNav = () => {
        if (!navbarOpen) {
          setNavbarOpen(true)
        }
      }
    

    Into this:

      const toggleNavbar = () => setNavbarOpen(!navbarOpen)
    

    And this:

    {navbarOpen ? <Hamburger open /> : <Hamburger />}
    

    Into this:

    <Hamburger open={navbarOpen} /> 
    

    And this:

          {navbarOpen ? (
            <Navbox>
                <ul>
                    <li>
                        <Link to="/services" >Services</Link>
                    </li> 
                    <li>
                        <Link to="/about">About</Link>
                    </li>
                    <li>
                        <Link to="/contact">Contact</Link>
                    </li>
                </ul>
                <IconComponent />
            </Navbox>
          ) : (
            <Navbox open>
                <ul>
                    <li>
                        <Link to="/services" onClick={closeNav}>Services</Link>
                    </li>
                    <li>
                        <Link to="/about">About</Link>
                    </li>
                    <li>
                        <Link to="/contact">Contact</Link>
                    </li>
                </ul>
            </Navbox>
          )}
    

    Into just this:

            <Navbox open={navbarOpen}>
                <ul>
                    <li>
                        <Link to="/services" onClick={closeNav}>Services</Link>
                    </li>
                    <li>
                        <Link to="/about">About</Link>
                    </li>
                    <li>
                        <Link to="/contact">Contact</Link>
                    </li>
                </ul>
            </Navbox>
          )}
    

    I will help you to debug much better (among other things).