Skip to content

Instantly share code, notes, and snippets.

@its-jman
Created January 28, 2021 03:36
Show Gist options
  • Select an option

  • Save its-jman/fa3330d1ff27d1818c4d06ee3d36c5b0 to your computer and use it in GitHub Desktop.

Select an option

Save its-jman/fa3330d1ff27d1818c4d06ee3d36c5b0 to your computer and use it in GitHub Desktop.
useMaxScroll.js
import { useEffect, useRef, useState } from "react";
import debounce from "lodash.debounce";
import useResizeObserver from "useResizeObserver";
const SINGLE_CHILD_WARNING = "Warning: Expected a single child node, aborting...";
const NO_NODE_WARNING = "Warning: useMaxPageScroll received an invalid node. Ignoring.";
/**
* Hook to prevent scrolling jitters. Watches the provided DOM node and
* maintains a min-height of the distance to the bottom of the screen, relative to
* your scroll position.
*
* This ensures that when nodes are removed from the page (eg. when filtering a table)
* the page will not suddenly shrink. It will only shrink once you have scrolled up.
*
* @param _node {Document | React.Ref}
*/
export default function useMaxPageScroll(_node = document) {
let node = _node;
const isRef = node?.hasOwnProperty("current");
if (isRef) node = node.current;
const aborted = useRef(false);
const [expectedHeight, setExpectedHeight] = useState(0);
const expectedHeightRef = useRef(expectedHeight);
expectedHeightRef.current = expectedHeight;
useResizeObserver(_node, (entry) => {
if (aborted.current) return;
setExpectedHeight(entry.contentRect.height);
});
useEffect(() => {
if (!isRef && !node) console.warn(NO_NODE_WARNING);
if (node) {
const listener = debounce((event) => {
if (aborted.current) return;
const scrollTarget = node === document ? node.scrollingElement : node;
const scrollBottom = scrollTarget.clientHeight + scrollTarget.scrollTop;
let heightTarget = node === document ? node.body : node.children;
if (heightTarget.length !== undefined) {
if (heightTarget.length !== 1) {
console.error(SINGLE_CHILD_WARNING);
aborted.current = true;
return;
}
heightTarget = heightTarget[0];
}
if (heightTarget) {
heightTarget.style.minHeight = `${scrollBottom}px`;
}
}, 20);
node.addEventListener("scroll", listener);
return () => node.removeEventListener("scroll", listener);
}
}, [node]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment