Making a Custom React Hook
code:
/**
* useScroll React custom hook
* Usage:
* const { scrollX, scrollY, scrollDirection } = useScroll();
*/
import { useState, useEffect } from "react";
export function useScroll() {
// storing this to get the scroll direction
const [lastScrollTop, setLastScrollTop] = useState(0);
// the offset of the document.body
const [bodyOffset, setBodyOffset] = useState(
document.body.getBoundingClientRect()
);
// the vertical direction
const [scrollY, setScrollY] = useState(bodyOffset.top);
// the horizontal direction
const [scrollX, setScrollX] = useState(bodyOffset.left);
// scroll direction would be either up or down
const [scrollDirection, setScrollDirection] = useState();
const listener = e => {
setBodyOffset(document.body.getBoundingClientRect());
setScrollY(-bodyOffset.top);
setScrollX(bodyOffset.left);
setScrollDirection(lastScrollTop > -bodyOffset.top ? "down" : "up");
setLastScrollTop(-bodyOffset.top);
};
useEffect(() => {
window.addEventListener("scroll", listener);
return () => {
window.removeEventListener("scroll", listener);
};
});
return {
scrollY,
scrollX,
scrollDirection
};
}
Styles
I am using css-in-javascript to set the visibility of the nav bar but you can use whatever you like.
const styles = {
active: {
visibility: "visible",
transition: "all 0.5s"
},
hidden: {
visibility: "hidden",
transition: "all 0.5s",
transform: "translateY(-100%)"
}
}
Finale code would look like this
import React from 'react';
import {useScroll} from './../../hooks/useScroll'
export default function Navbar() {
const { y, x, scrollDirection } = useScroll();
const styles = {
active: {
visibility: "visible",
transition: "all 0.5s"
},
hidden: {
visibility: "hidden",
transition: "all 0.5s",
transform: "translateY(-100%)"
}
}
return (
<nav className="Header" style={scrollDirection === "down" ? styles.active: styles.hidden} >
<Link to="/" className="Header__link">
<img src={Logo} height="50px" width="auto" alt="logo"/>
<div className="Header__link__title">
Chronology
</div>
</Link>
<ul className="flex">
<li>
<Link to="/about" className="Header__link">About</Link>
</li>
<li>
<Link to="/blog" className="Header__link">Blogs</Link>
</li>
</ul>
</nav>
)
}
I know this is very lazy writing but it's already midnight.
I will write in detail about it.
I hope it help someone.
Hi, there are a couple of things here that could lead to some Jank so I thought I'd give you some pointers, I hope that's alright 😊.
You're
listener
function is outside of the useEffect it's used in. This means it is remade on every draw, this isn't a huge problem with onclick events and stuff but when it comes to using them in a useEffect it means the the useEffect runs every draw (as does its return functions).In this instance you've not got a dependencies array on your useEffect anyway meaning it is ran every draw. It would be better to have an empty array as the dependencies as that means it will only run on mount and then return on unmount.
You're calling
getBoundingClientRect
on each scroll that's quite a heavy function to be calling so often. It's worth remembering that window already knows how far it's scrolled so we can just get it off that.A couple of extra points I'd add in are the scroll event listener is heavy as it is so I'd include a context with your hook that a dev can use to use the same listener else where in the app and on the same note you could make the function more generic by giving not just the current x and y but also the last x and y then you don't need to include the direction as it's a simple calculation the dev can include else where.
I think with just those couple of changes you could change the hook from a great concept with a good execution to a great concept with a great execution. I've made a quick demo with my changes for you to have a look at 😊