Build a sticky navigation bar with React
Tags: react, javascript, beginners, webdev
Published: 2020-01-23 17:49:43

I'm pretty sure that you've already seen the effect we are going to make today - It's a common animation that we see on a lot of websites. When the user scrolls, the navigation bar moves down with a cool animation effect.

You're lucky today because, in this post, we'll replicate the same effect with React by building a sticky navbar from scratch with a custom hook.

You can check it live [here](https://codepen.io/ibrahima92/full/wvBNmGv)

_Originally posted on [my blog](https://www.ibrahima-ndaw.com/blog/build-a-sticky-nav-with-react/)_

## Setting up the project

To be able to follow along, you need to create a fresh React app by running the following command:

```shell
npx create-react-app react-sticky-navbar
```

Next, structure your folder as follows.

```
src
├── App.js
├── App.test.js
├── assets
|  └── images
|     └── logo.svg
├── components
|  └── Header
|     ├── About.js
|     ├── Navbar.css
|     ├── Navbar.js
|     ├── Welcome.css
|     └── Welcome.js
├── hooks
|  └── useSticky.js
├── index.css
├── index.js
├── serviceWorker.js
└── setupTests.js
```

I will focus mostly on the navbar related files to make this post short and useful. You can still find the source code at the end of the article.

Let's now get hands dirty by writing some code.

- `Header/Welcome.js`

```jsx
import React from "react"

import "./Welcome.css"
import Logo from "../../assets/images/logo.svg"
import About from "./About"

const Welcome = ({ element }) => {
  return (
    <main>
      <section className="welcome">
        <div ref={element}>
          <img src={Logo} alt="logo" className="welcome--logo" />
          <p>Even if you scroll, i will stick with you</p>
          <button className="welcome__cta-primary">Contact us</button>
        </div>
      </section>
      <About />
    </main>
  )
}

export default Welcome
```

As you can see, here we have a simple component that receives the props `element`. This last is the reference of the element that will fire the sticky effect later on the scrolling.

By the way, here I use destructuring to pull out the element. If you want too, you can use `props.stickyRef`.

Now, let's move on to the next file and create the navigation bar skeleton.

- `Header/Navbar.js`

```jsx
import React from "react"
import "./Navbar.css"
import Logo from "../../assets/images/logo.svg"

const Navbar = () => (
  <nav className="navbar">
    <div className="navbar--logo-holder">
      <img src={Logo} alt="logo" className="navbar--logo" />
      <h1> Stick'Me</h1>
    </div>
    <ul className="navbar--link">
      <li className="navbar--link-item">Home</li>
      <li className="navbar--link-item">About</li>
      <li className="navbar--link-item">Blog</li>
    </ul>
  </nav>
)
export default Navbar
```

Here, we have for now a very simple component. But later we will update it to be able to display some elements conditionally. And also make the navigation bar sticky.

![gif-1](https://media.giphy.com/media/3oz8xQQP4ahKiyuxHy/source.gif)

## The sticky effect

For the sticky effect, we will create a custom hook to handle the effect and then use it in our component and at the same time, make it reusable.

- `hooks/useSticky.js`

```jsx
import { useEffect, useState, useRef } from "react"

function useSticky() {
  const [isSticky, setSticky] = useState(false)
  const element = useRef(null)

  const handleScroll = () => {
    window.scrollY > element.current.getBoundingClientRect().bottom
      ? setSticky(true)
      : setSticky(false)
  }

  // This function handle the scroll performance issue
  const debounce = (func, wait = 20, immediate = true) => {
    let timeOut
    return () => {
      let context = this,
        args = arguments
      const later = () => {
        timeOut = null
        if (!immediate) func.apply(context, args)
      }
      const callNow = immediate && !timeOut
      clearTimeout(timeOut)
      timeOut = setTimeout(later, wait)
      if (callNow) func.apply(context, args)
    }
  }

  useEffect(() => {
    window.addEventListener("scroll", debounce(handleScroll))
    return () => {
      window.removeEventListener("scroll", () => handleScroll)
    }
  }, [debounce, handleScroll])

  return { isSticky, element }
}

export default useSticky
```

All the magic will happen here (i promise).
We first need to import a couple of hooks from React. And next, define our state with `useState()` and set the initial state to `false`. That means, now, we'll be able to switch between `true` and `false` depending on the scrolling.

![gif-2](https://media.giphy.com/media/czSeUABBSqxSU/source.gif)

When the user starts scrolling, the function `handleScroll()` will be called. And, it checks if the `window.scrollY > stickyRef.current.getBoundingClientRect().bottom` and handles the `isSticky` state.
In other words, it will check if the number of pixels the page has currently scrolled along the vertical axis is superior or not to the position of the current element relative to its bottom.

Next, we use a debounce function to throttle the scrolling event and avoid performance issues. This function, instead of running `handleScroll` all the time, will just run every 20 milliseconds to give you more control.

With that, we can now listen to the scroll event when the component is mounted and remove listeners when unmounted.

Great! now to make our custom hook usable in other files, we need to return something from it. Here we need to return the `isSticky` state and `element` which allows having an element reference.

- `Header/Navbar.js`

```jsx
import React from "react"
import "./Navbar.css"
import Logo from "../../assets/images/logo.svg"

const Navbar = ({ sticky }) => (
  <nav className={sticky ? "navbar navbar-sticky" : "navbar"}>
    <div className="navbar--logo-holder">
      {sticky ? <img src={Logo} alt="logo" className="navbar--logo" /> : null}
      <h1> Stick'Me</h1>
    </div>
    <ul className="navbar--link">
      <li className="navbar--link-item">Home</li>
      <li className="navbar--link-item">About</li>
      <li className="navbar--link-item">Blog</li>
    </ul>
  </nav>
)
export default Navbar
```

As you can see, this file receives now as props the `sticky` state. We can now check if it's `true` or `false` and show classes or elements conditionally with the help of the ternary operator.

We have covered a lot, however, it remains an important part: styling and animations. Let's do that in the next section.

![gif-3](https://media.giphy.com/media/UO5elnTqo4vSg/source.gif)

## Styling the navbar

- In `Navbar.css`

```css
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 2.5rem;
  position: absolute;
  z-index: 1;
  width: 100%;
}

.navbar-sticky {
  background: #333;
  position: fixed;
  top: 0;
  left: 0;
  box-shadow: 1px 1px 1px #222;
  animation: moveDown 0.5s ease-in-out;
}

.navbar--logo {
  width: 2rem;
  height: 2rem;
  margin-right: 0.5rem;
  animation: rotate 0.7s ease-in-out 0.5s;
}

@keyframes moveDown {
  from {
    transform: translateY(-5rem);
  }
  to {
    transform: translateY(0rem);
  }
}

@keyframes rotate {
  0% {
    transform: rotateY(360deg);
  }
  100% {
    transform: rotateY(0rem);
  }
}
```

Here, besides fixing the navigation bar on scroll with the `.navbar-sticky` class, we use `moveDown` to make the animation effect which also rotates the logo a little bit to make everything look good and smooth on the scrolling.

With that, we can now use the `App.js` file to display our components when the page loads.

- `App.js`

```jsx
import React from "react"
import useSticky from "./hooks/useSticky.js"
import Welcome from "./components/Header/Welcome"
import Navbar from "./components/Header/Navbar"

function App() {
  const { isSticky, element } = useSticky()
  return (
    <>
      <Navbar sticky={isSticky} />
      <Welcome element={element} />
    </>
  )
}

export default App
```

As you can see, here, we import our components and the custom hook which allows us to pass down props and handle the sticky effect appropriately.

With that, we've now done building our sticky navbar using React JS.

Thanks for reading it.

You can find the [Source code here](https://github.com/ibrahima92/react-sticky-navbar)

![applause](https://media.giphy.com/media/fnK0jeA8vIh2QLq3IZ/source.gif)


| [TWITTER](https://twitter.com/ibrahima92_) | [BLOG](https://www.ibrahima-ndaw.com/) | [NEWSLETTER](https://ibrahima-ndaw.us5.list-manage.com/subscribe?u=8dedf5d07c7326802dd81a866&id=5d7bcd5b75) | [GITHUB](https://github.com/ibrahima92) | [LINKEDIN](https://www.linkedin.com/in/ibrahima-ndaw/) | [CODEPEN](https://codepen.io/ibrahima92) | [DEV](https://dev.to/ibrahima92) |
| -------------- | ------------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------- | ------------------------------------------------------ | ---------------------------------------- | -------------------------------- |