dark mode

Create sticky header only with React Hooks.

Create sticky header only with React Hooks.

Create a simple sticky header only with functional components and React Hooks with no npm packages or other complicated functionality.

I love React Hooks and I try to do everything with them without using component classes and their lifecycle.

If we don't worry about browsers' support, we can use only CSS to give sticky properties to an element, for example:

.sticky {
  position: sticky;
  top: 0;
  z-index: 100; /* this is optional and should be different for every project */
}

If we need better support we will need one parent component to scroll and one child that will stick also and a CSS file.

Child component

import React from 'react';

export default () => <h1 className="sticky-inner">Sticky</h1>;

This is the actual element that we want to be sticky. The component is a simple functional component that returns <h1> tag, the only thing that we add is a sticky-inner class for the CSS styling.

CSS file

.sticky-wrapper {
  position: relative;
  height: 3rem; /* We need to change this value */
}

.sticky .sticky-inner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
}

Here we have class sticky-wrapper. We wrap the sticky element with <div>, because it’s easier. I believe we don’t need to over-engineer simple things. The element will be our anchor from which we will position our sticky component, that is why we need position: relative. We have height property, this is important to not have jumping glitches we need to preserve the height of the sticky wrapper. The height value must be the same as the sticky element. For example, if our header has a height of 3rem, we need to set the wrapper with 3rem.

Next class is .sticky .sticky-inner. We will add and remove .sticky class dynamically. When the sticky wrapper has additional sticky class, the inner element will have new properties that makes the sticky element stick to the top of the browser window.

Parent component

import React, { Fragment, useEffect, useRef, useState } from 'react';
import Sticky from './sticky';

export default () => {
  const [isSticky, setSticky] = useState(false);
  const ref = useRef(null);
  const handleScroll = () => {
    if (ref.current) {
      setSticky(ref.current.getBoundingClientRect().top <= 0);
    }
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);

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

  return (
    <Fragment>
      <p>Lorem ipsum...</p>
      <div className={`sticky-wrapper${isSticky ? ' sticky' : ''}`} ref={ref}>
        <Sticky />
      </div>
      <p>Lorem ipsum...</p>
    </Fragment>
  );
};

In the parent component we have isSticky variable which will show if the component is sticky or not. We control its state with useState Hook.

We need to get component's reference to track its position relative to the browser's window we. If we get the ref directly, the browser will show a console error such as:

Warning: Function components cannot be given refs.
Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

In our case we don't want to over-complicate things. So I will skip the recommendation of React.forwardRef() and wrap the component with <div> tag.

With string interpolation and ternary operator we add the class sticky when isSticky variable is true.

To simulate with hook react's lifecycle method componentDidMount() we use useEffect() which will take a function and an empty array as arguments.

useEffect(() => {
  // run once the component is loaded - componentDidMount()
}, []);

To simulate the lifecycle method componentWillUnmount() we use the same useEffect() with an empty array. The difference is that we return a function which will run only after the component is not in the DOM.

useEffect(() => {
  // run once the component is loaded - componentDidMount()
  return () => {
    // run after the component is removed - componentWillUnmount()
  };
}, []);

This is a good place to add and remove our Event Listeners.

In the scroll event listener we will call out handleScroll method, which will set isSticky variable when the component wrapper is equal to the browser's top edge. We are making, and additional check to make sure that our sticky element is loaded in the DOM.

Knowing the basics will let us extend our functionality from now on with additional settings to meet every requirement and forget about complicated libraries or heavy dependencies.

Related articles

© 2021 All rights reserved.