useLockedBody() hook in React

Implement the useLockedBody() hook in React that will lock the body from further scrolling.

The useLockedBody() hook will take the reference of the parent and return the lock state and the method that will toggle the lock state.

To lock the body, we will have to remove the overflow from the body so that everything inside it is prevented from scrolling and hide the scrollbar.

To hide the scroll bar, get the scrollWidth of the referenced element and add same size right padding to the body to cover the gap.

This complete processing will take inside useLayoutEffect() hook as the side effect is with the DOM.

Use a state to monitor the toggling and depending upon the toggle state, lock or unlock the body.

const useLockedBody = (ref, initiallyLocked = false) => {
  const [locked, setLocked] = useState(initiallyLocked);

  // handle side effects before render
  useLayoutEffect(() => {
    if (!locked) {
      return;
    }

    // save original body style
    const originalOverflow = document.body.style.overflow;
    const originalPaddingRight = document.body.style.paddingRight;

    // lock body scroll
    document.body.style.overflow = "hidden";

    // get the scrollBar width
    const root = ref.current; // or root
    const scrollBarWidth = root ? root.offsetWidth - root.scrollWidth : 0;

    // prevent width reflow
    if (scrollBarWidth) {
      document.body.style.paddingRight = `${scrollBarWidth}px`;
    }

    // clean up
    return () => {
      document.body.style.overflow = originalOverflow;

      if (scrollBarWidth) {
        document.body.style.paddingRight = originalPaddingRight;
      }
    };
  }, [locked]);

  // update state when dependecy changes
  useEffect(() => {
    if (locked !== initiallyLocked) {
      setLocked(initiallyLocked);
    }
  }, [initiallyLocked]);

  return [locked, setLocked];
};
Input:
const Example = () => {
  const ref = useRef();

  // call the hook which returns, current value and the toggler function
  const [locked, setLocked] = useLockedBody(ref);

  return (
    <div style={{ height: "200vh" }} id="abc" ref={ref}>
      <button onClick={() => setLocked(!locked)}>{locked ? "unlock scroll" : "lock scroll"}</button>
    </div>
  );
};

// click on the button to lock and unlock body locking