DEV Community

Cover image for Gatsby Navigation using Styled Components and useState hook
Wojciech Snopkowski
Wojciech Snopkowski

Posted on • Originally published at snopkowski.com

Gatsby Navigation using Styled Components and useState hook

In this walkthrough, I'll guide you through the steps of building a simple navigation bar with GatsbyJS. We'll use Styled Components to style our application.

mobile-nav

Motivation

I was trying to find an extremely simple and responsive Gatsby navigation bar that utilizes Styled components and useState hook but ended up creating my own. I'll be happy if there's at least one person that finds this tutorial somewhat useful.

Requirements

If you've found this tutorial, I assume that you've already got node and gatsby-cli installed on your machine.

node - check in terminal by typing:

node -v
Enter fullscreen mode Exit fullscreen mode

gatsby-cli if not, use your terminal to install:

npm install -g gatsby-cli
Enter fullscreen mode Exit fullscreen mode

Installation

Begin with creating a new project, we'll call it gatsby-navigation and use a default starter. I intended to change as little as possible inside the starter.

gatsby new gatsby-navigation https://github.com/gatsbyjs/gatsby-starter-default
Enter fullscreen mode Exit fullscreen mode

gatsby-installation

It's time to navigate into the new site directory.

cd gatsby-navigation
Enter fullscreen mode Exit fullscreen mode

Styled Components

Styled components is a CSS-in-JS framework, which enables you to write plain CSS utilizing template literals in your components without worrying about name collisions - meaning, it simply generates unique class names for styles. Components can be dynamically styled based on props or a theme. You don't have to worry about vendor prefixes. Furthermore, it takes advantage of the Critical CSS technique - ships the bare minimum required to render a page.

Install in terminal:

npm install --save gatsby-plugin-styled-components styled-components babel-plugin-styled-components
Enter fullscreen mode Exit fullscreen mode

Don't forget to add 'gatsby-plugin-styled-components' to plugins array in gatsby-config.js

Start Building

Since we've got everything already installed, it's time to start developing our navbar.

gatsby develop
Enter fullscreen mode Exit fullscreen mode

Let's start by creating a directory named Navbar with Logo.js, Navbar.js and NavbarLinks.js inside.

source-folder

Logo

Almost every navigation needs a logo so we'll use GraphQL with gatsby-image to grab our logo from images directory.

gatsby-image enables us to optimize images without tons of tedious work.

// Logo.js

import React from "react"
import Img from "gatsby-image"
import { useStaticQuery, graphql } from "gatsby"


const Logo = () => {
  const data = useStaticQuery(graphql`
    query {
      file(name: { eq: "gatsby-icon" }, extension: { eq: "png" }) {
        childImageSharp {
          fluid(maxWidth: 50, pngQuality: 80) {
            ...GatsbyImageSharpFluid
          }
        }
      }
    }
  `)
  return (
      <Img fluid={data.file.childImageSharp.fluid} alt="logo" />
  )
}

export default Logo

Enter fullscreen mode Exit fullscreen mode

Navbar Links

Link is a special component designed to link between internal pages instead of using <a> tags.
What is so special about it? Simply, it prefetches our pages.

// NavbarLinks.js

import React from "react"
import { Link } from "gatsby"

const NavbarLinks = () => {
  return (
    <>
      <Link to="/">About</Link>
      <Link to="/404">Services</Link>
      <Link to="/">Gallery</Link>
      <Link to="/404">Contact</Link>
    </>
  )
}

export default NavbarLinks

Enter fullscreen mode Exit fullscreen mode

Navigation Bar

The component that holds most of the logic of our navbar. We'll use a conditional (ternary) operator to switch between props and a useState hook to hold the state of our navigation.

// Navbar.js

import React, { useState } from "react"
import NavbarLinks from "./NavbarLinks"
import Logo from "./Logo"
import styled from 'styled-components'

const Navigation = styled.nav``
const Toggle = styled.div``
const Navbox = styled.div``
const Hamburger = styled.div``


const Navbar = () => {
  const [navbarOpen, setNavbarOpen] = useState(false)

  return (
    <Navigation>
      <Logo />
      <Toggle
        navbarOpen={navbarOpen}
        onClick={() => setNavbarOpen(!navbarOpen)}
      >
        {navbarOpen ? <Hamburger open /> : <Hamburger />}
      </Toggle>
      {navbarOpen ? (
        <Navbox>
          <NavbarLinks />
        </Navbox>
      ) : (
        <Navbox open>
          <NavbarLinks />
        </Navbox>
      )}
    </Navigation>
  )
}

export default Navbar

Enter fullscreen mode Exit fullscreen mode

You've got it!

Congratulations! By this time our application should look extraordinarily bad, but the guts are already in place...

celebrate

Let's add some styling

Aside from making it responsive, it is time to use the good stuff that comes with Styled components. In our case, we'll use props to determine which styles should be active.

Final Logo

We've added a wrapper to enclose our image. You'd most likely use it to take us to our homepage so we'll make it treat our wrapper as a link.

// Logo.js
import React from "react"
import styled from "styled-components"
import Img from "gatsby-image"
import { Link, useStaticQuery, graphql } from "gatsby"

const LogoWrap = styled.div`
  margin: auto 0;
  flex: 0 1 36px;

  @media (max-width: 768px) and (orientation: landscape) {
    flex: 0 1 25px;
  }
`
const Logo = () => {
  const data = useStaticQuery(graphql`
    query {
      file(name: { eq: "gatsby-icon" }, extension: { eq: "png" }) {
        childImageSharp {
          fluid(maxWidth: 50, pngQuality: 80) {
            ...GatsbyImageSharpFluid
          }
        }
      }
    }
  `)

  return (
    <LogoWrap as={Link} to="/">
      <Img fluid={data.file.childImageSharp.fluid} alt="logo" />
    </LogoWrap>
  )
}

export default Logo

Enter fullscreen mode Exit fullscreen mode

Final Navbar Links

Let's make a new component that's based on a Link component and style it.

// NavbarLinks.js

import React from "react"
import styled from "styled-components"
import { Link } from "gatsby"

const NavItem = styled(Link)`
  text-decoration: none;
  color: #111;
  display: inline-block;
  white-space: nowrap;
  margin: 0 1vw;
  transition: all 200ms ease-in;
  position: relative;

  :after {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    width: 0%;
    content: ".";
    color: transparent;
    background: goldenrod;
    height: 1px;
    transition: all 0.4s ease-in;
  }

  :hover {
    color: goldenrod;
    ::after {
      width: 100%;
    }
  }

  @media (max-width: 768px) {
    padding: 20px 0;
    font-size: 1.5rem;
    z-index: 6;
  }
`
const NavbarLinks = () => {
  return (
    <>
      <NavItem to="/">About</NavItem>
      <NavItem to="/404">Services</NavItem>
      <NavItem to="/">Gallery</NavItem>
      <NavItem to="/404">Contact</NavItem>
    </>
  )
}

export default NavbarLinks

Enter fullscreen mode Exit fullscreen mode

Final Navigation Bar

Now add styling based on props passed into our Hamburger component using template literals.


// Navbar.js

import React, { useState } from "react"
import styled from "styled-components"
import NavbarLinks from "./NavbarLinks"
import Logo from "./Logo"

const Navigation = styled.nav`
  height: 10vh;
  display: flex;
  background-color: #fff;
  position: relative;
  justify-content: space-between;
  text-transform: uppercase;
  border-bottom: 2px solid #33333320;
  margin: 0 auto;
  padding: 0 5vw;
  z-index: 2;
  align-self: center;

  @media (max-width: 768px) {
    position: sticky;
    height: 8vh;
    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;

  @media (max-width: 768px) {
    flex-direction: column;
    position: fixed;
    width: 100%;
    justify-content: flex-start;
    padding-top: 10vh;
    background-color: #fff;
    transition: all 0.3s ease-in;
    top: 8vh;
    left: ${props => (props.open ? "-100%" : "0")};
  }
`

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

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

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

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

  return (
    <Navigation>
      <Logo />
      <Toggle
        navbarOpen={navbarOpen}
        onClick={() => setNavbarOpen(!navbarOpen)}
      >
        {navbarOpen ? <Hamburger open /> : <Hamburger />}
      </Toggle>
      {navbarOpen ? (
        <Navbox>
          <NavbarLinks />
        </Navbox>
      ) : (
        <Navbox open>
          <NavbarLinks />
        </Navbox>
      )}
    </Navigation>
  )
}

export default Navbar


Enter fullscreen mode Exit fullscreen mode

Wrapping up

Combining styled components with useState hook is a modern way of writing React’s code, it’s quite easy to implement and gives our app a clean look.

Feel free to share if you found it useful.

Sandbox

Links

Read @ Snopkowski.com

Github repo

Live demo

Top comments (0)