Fortune Ikechi Fortune Ikechi is a frontend engineer based in Rivers State, Nigeria. He is a student of the University of Port Harcourt. He is passionate about community and software engineering processes.

Using React Hooks to create sticky headers

5 min read 1445

React Logo Over a Sandy Background

For many developers, implementing a sticky header on a table before the CSS property position: sticky was challenging. In this article, we are going to look at building a sticky header using React Hooks.

Introduction

When creating a sticky header for tables, most frontend developers use the <table /> component and the CSS position property, however, in this post we are going to build a table with a sticky header using native React Hooks.

Prerequisites

This tutorial assumes that the reader has:

  • Node.js and NPM installed on your machine
  • Basic knowledge of JavaScript and how React works

Begin by installing the official React application scaffold tool, create-react-app:

npm install -g create-react-app

You can verify the installation by running create-react-app this should ask you to specify the directory name, in our case, we will be building a table of countries with a sticky header that’s built by React Hooks. We will be building the application as a <Table /> component that takes in the data for the header table and the data for the application, in our case, the <Table /> will take in a list of countries and some information about the country when hovered on by the user.

Building a sticky header table

React allows the use of hooks with functional components, and as a result, we will be creating the following files:

  • Table.js — This component will contain the table and sticky hook for our application
  • Data.js — This component will contain the data to be passed as props in our application
  • stickyHeader.js — This will be a custom hook for making our header sticky

Getting started

We will be using create-react-app to bootstrap our project. To create a new project using the create-react-app boilerplate, run the command in your preferred terminal:

create-react-app sticky-header-app

The name “sticky-header-app” is used as our project name for this tutorial, it can be replaced with whatever name you choose to use.

Next, navigate into your project directory and start your development server by running:

cd sticky-heade-app && npm start 

The command above opens up a browser tab that will display the default boilerplate application.

Building the data component

In the src folder, let’s create a new file Data.js. Note that the file can be whatever you want and how you see fit.

Here, we export a constant HeaderTable, an array with country, code, and area as objects, next we export another array of objects that contain a few countries, code, and their total land area:

export const HeaderTable = ["Country", "Code", "Area"];
export const tableData = [
  {
    country: "Albania",
    code: "ALB",
    area: "28748 km2"
  },
  {
    country: "Algeria",
    code: "AG",
    area: "2381740 km2"
  },
  {
    country: "Brazil",
    code: "BR",
    area: "8,515,767 km2",
  },
  {
    country: "Bulgaria",
    code: "BG",
    area: "110,993.6 km2",
  },
  {
    country: "China",
    code: "CN",
    area: "9,596,961 km2",
  },
  {
    country: "Denmark",
    code: "DK",
    area: "42,933 km2",
  },
  {
    country: "USA",
    code: "US",
    area: "9,833,520 km2",
  }
];
export const countryData = [
  {
    country: "China", 
    data: `China, is a country
          in East Asia. It is the world's most populous country, with a
          population of around 1.4 billion in 2019.`
  },
  {
    country: "Albania", 
    data: `The land was first settled by a tribe of people called 
    the Illyrians in about 2000 BC. The Romans were one of the 
    first empires to conquer the land and Albania was part of first 
    the Roman Empire and then later the Byzantine Empire or Eastern Roman Empire.`
  },
  {
    country: "Algeria", 
    data: `In ancient times Algeria was known as Numidia. 
    The Numidians were known for their army which rode horses, or cavalry. 
    During the Middle Ages, Algeria was lead by various tribes and Berber dynasties`
  },
  {
    country: "Brazil", 
    data: `Brazil was officially "discovered" in 1500, when a fleet commanded by 
    Portuguese diplomat Pedro Álvares Cabral, on its way to India, landed in Porto 
    Seguro, between Salvador and Rio de Janeiro.`
  },
  {
    country: "Bulgaria", 
    data: `Bulgarian history really starts with the Thracians. 
    The Thracians were a group of Indo-European tribes that 
    lived throughout the Balkan Peninsula from about 1000 BC.`
  },
  {
    country: "Denmark", 
    data: `Denmark has been inhabited by the Danes since its early prehistoric history. The Vikings rose to power during the 9th century. 
    Over the next 300 years Vikings would raid and explore areas around Denmark all the way to the island of England.`
  },
  { 
    country: "USA", 
    data: `The United States of America (USA) is a sovereign country in North America. 
    It is commonly called the United States (U.S.) or America. There are forty-eight states 
    that border each other and Washington, D.C., the capital district. These states are between 
    the Pacific and Atlantic Oceans.`
  },
]

In the code above, we initialized an array called HeaderTable in which the data for our application which includes country, their code, and area. We also exported a data object which includes some information on the country. Next, we will build our custom StickyHeader hook.

Building the StickyHeader hook

In this component, we will build a custom hook sticky header for our header table, we will add event listeners to handle scroll and enforce stickiness:

import { useState, useEffect, useRef, useCallback } from "react";
const StickyHeader = (defaultSticky = false) => {
  const [isSticky, setIsSticky] = useState(defaultSticky);
  const tableRef = useRef(null);
  const toggleSticky = useCallback(
    ({ top, bottom }) => {
      if (top <= 0 && bottom > 2 * 68) {
        !isSticky && setIsSticky(true);
      } else {
        isSticky && setIsSticky(false);
      }
    },
    [isSticky]
  );
  useEffect(() => {
    const handleScroll = () => {
      toggleSticky(tableRef.current.getBoundingClientRect());
    };
    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, [toggleSticky]);
  return { tableRef, isSticky };
};
export default StickyHeader;

In the code block above, we imported the native React useState update the initial state of the application, which we’ve defined as isSticky, next we initialized the object tableRef as a reference to the table element.

We initialized a constant handleScroll to detail the position of the table on a screen and check for the current size of the header. The number 65 is the height of the header, the if…else statement simply checks for the value of isSticky and returns a new value if it changes.

Finally, we added an event listener to handle the user’s scroll events, next we’ll build the Table component to handle the user data as props and render our application.

Building the table component

In this component, we will be building a functional component, that takes in parameters from our StickyHeader component and renders our application:

import React, { useState } from "react";
import StickyHeader from "./StickyHeader.js";
import { countryData } from "./Data";
export default function Table({ headers = [], data = [] }) {
  const { tableRef, isSticky } = StickyHeader();
  const [display, setDisplay] = useState(false);
  const [countryDetails, setData] = useState({
    countryHistroy: null,
  });

  const openDetails= (e) => {
    countryData.forEach(details => {
      if (details.country === e.target.textContent) {
        setData({
          countryHistroy: details.data,
        })
      }
    });
    setDisplay(true);
  };
  const closeDetails= () => {
    setDisplay(false);
  };
  const renderHeader = () => (
    <thead>
      <tr>
        {headers.map((item) => (
          <th key={item}>{item}</th>
        ))}
      </tr>
    </thead>
  );
  return (
    <div>
      {isSticky && (
        <table
          className="sticky"
          style={{
            position: "fixed",
            top: 0,
            left: 0,
            right: 0
          }}
        >
          {renderHeader()}
        </table>
      )}
      <table ref={tableRef}>
        {renderHeader()}
        <tbody>
          {data.map((item) => (
            <tr key={item.code}>
              <td className="country" onMouseEnter={openDetails} onMouseOut={closeDetails}>{item.country}</td>
              <td>{item.code}</td>
              <td>{item.area}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <div className="country-details" style={{display: display ? "block" : "none"}}>
          <p className="country-description">
          {countryDetails.countryHistroy}
          </p>
        </div>
    </div>
  );
}

In this component, we initialized the StickyHeader component using two parameters, tableRef and isSticky, the tableRef references the table element where we’ll have a sticky header while the isSticky checks the scroll events in the page.



The <table /> component takes the .sticky style to differentiate the sticky header from the original table, to conclude our application we’ll need to update our App.js file to render our individual components using the Table component like this:

import React from "react";
import Table from "./Table";
import { HeaderTable, tableData } from "./Data";
import "./styles.css";
export default function App() {
  return (
    <div className="App">
      <Table headers={HeaderTable} data={tableData} />
    </div>
  );
}

In the code block above, we are first importing our HeaderTable and tableData , next, we rendered the Table component, passing the headerTable and tableData as props.

At the end of this, our app header should remain sticky while we scroll through our countries table, it should look like the video below:

Sticky Header Table

Conclusion

In this post, we’ve learned how to use hooks to build a sticky header, we’ve also seen how to pass data from components and render it as props. You can read more React articles here on the LogRocket blog. The code used in this article can be found on CodeSandbox.

Get setup with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Fortune Ikechi Fortune Ikechi is a frontend engineer based in Rivers State, Nigeria. He is a student of the University of Port Harcourt. He is passionate about community and software engineering processes.

One Reply to “Using React Hooks to create sticky headers”

  1. I recommend passing the ref to the hook instead of from the hook to the implementation. It’s a little more resilient bc you know exactly what the ref is.

Leave a Reply