Creating a reading progress bar in React
Tags: react, javascript
Published: 2019-10-11 07:37:42

---
title: Creating a reading progress bar in React
published: true
tags: React, JavaScript
canonical_url: https://nehalist.io/creating-a-reading-progress-bar-in-react/
---

 ![Creating a reading progress bar in React](https://ghost.nehalist.io/content/images/2019/07/react-reading-bar-1.png)

Reading progress bars, like the one you can find on my blog at the top of single posts, are a nice little addition to give detailed information about how far the reader has progressed on the current post. The scrolling bar is not really meaningful in this regard; it includes your entire page, which means that your header, comments, footer, etc. are part of the indication.

Creating a reading progress bar which tells you the actual progress of just the current post content in React is quite easy - especially with hooks, which make our component even smaller.

## The ReadingProgress component

Our `ReadingProgress` component will do the following things:

- make use of the `useState` hook which will be responsible for reading and setting our reading progress
- make use of the `useEffect` hook which will be responsible for handling the scroll event and properly update our progress bar on scroll
- return the reading progress bar with the proper width

So let's dive right into the implementation:

```jsx
const ReadingProgress = ({ target }) => {
  const [readingProgress, setReadingProgress] = useState(0);
    
  return <div className={`reading-progress-bar`} style={{width: `${readingProgress}%` }} />
};
```

This is the foundation for our component. `readingProgress` will be used as width (in percent) for our progress bar. The only prop for our component is `target`, which will be a reference to our DOM container of the post - more on that in a few moments.

First let's implement our listener, which will update our progress bar on scroll events:

```jsx
const scrollListener = () => {
    if (!target.current) {
      return;
    }

    const element = target.current;
    const totalHeight = element.clientHeight - element.offsetTop;
    const windowScrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;

    if (windowScrollTop === 0) {
      return setReadingProgress(0);
    }

    if (windowScrollTop > totalHeight) {
      return setReadingProgress(100);
    }
    
    console.log(windowScrollTop);

    setReadingProgress((windowScrollTop / totalHeight) * 100);
  };
```
*Will be placed within our `ReadingProgress` component.*

`windowScrollTop` tries a bunch of different values which fixes `undefined` values for some browsers (e.g. Safari).

There's one problem with this implementation: 100% reading progress is only achieved if we've scrolled _past_ our target. That's pretty unlikely to be true (except you scroll one line after finished reading one line, which would make you really weird) - so we need to slightly adjust how our reading progress is calculated:

```jsx
const totalHeight = element.clientHeight - element.offsetTop - window.innerHeight;
```

This should yield to a more accurate result in terms of when the bar should show finished.

Next we'll put our listener into a `useEffect` hook, which makes our entire component to look like this:

```jsx
const ReadingProgress = ({ target }) => {
  const [readingProgress, setReadingProgress] = useState(0);
  const scrollListener = () => {
    if (!target.current) {
      return;
    }

    const element = target.current;
    const totalHeight = element.clientHeight - element.offsetTop - (window.innerHeight);
    const windowScrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;

    if (windowScrollTop === 0) {
      return setReadingProgress(0);
    }

    if (windowScrollTop > totalHeight) {
      return setReadingProgress(100);
    }

    setReadingProgress((windowScrollTop / totalHeight) * 100);
  };
  
  useEffect(() => {
    window.addEventListener("scroll", scrollListener);
    return () => window.removeEventListener("scroll", scrollListener);
  });

  return <div className={`reading-progress-bar`} style={{width: `${readingProgress}%`}} />;
};
```

The returned function from our `useEffect` hook is basically just what happens when the component is unmounted (see [Effects with Cleanup](https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup) in the docs).

Last but not least we need to use our component somewhere. At this point we'll need create a ref on our target container and simply pass this to our `ReadingProgress` component:

```jsx
function App() {
  const target = React.createRef();
  return (
    <>
      <ReadingProgress target={target} />
      <div className={`post`} ref={target}>post content</div>
    </>
  );
}
```
See the [docs](https://reactjs.org/docs/refs-and-the-dom.html) for further information about `createRef`

Now your reading progress bar should work perfectly fine - except that you can't see it because it has no height. Fix this by adding some CSS:

```css
.reading-progress-bar {
  position: sticky;
  height: 5px;
  top: 0;
  background-color: #ff0000;
}
```

**Done!** Now your readers no longer get lost in the sheer unending length of your posts and always know when it'll be over.

To see a fully working example you can take a look at this code pen:

{% codepen https://codepen.io/twhite96/pen/agRNYZ %}

## Third party packages

There are some third party packages out there which handle this exact problem. As far as I've found out most of them are outdated and/or no longer maintained - but what's even more relevant at this point: do you really need a third party dependency for a really simple component with around 30 lines of code? Well, honestly, _I don't think so_.

## Conclusion

As you've seen implementing a reading progress bar in React is pretty easy. Thanks to hooks we can implement this component as a very small function component with little to no overhead.

---

If you liked this post feel free to leave a ❤, follow me on [Twitter](https://twitter.com/nehalist) and subscribe to [my newsletter](https://nehalist.io/newsletter). This post was originally published on [nehalist.io](https://nehalist.io/creating-a-reading-progress-bar-in-react/).