useLockedBody()

This React hook is used to block the scrolling of the page.

A good example of a use case is when you need to open a modal.


For flexibility, this hook offers 2 APIs:

  • Use it as we would use a useState (example 1)
  • Use it with our own logic, coming from a props or redux for example (example 2)

Finally, you can optionally change the reflow padding if you have another sidebar size than the default (15px)

The Hook

1import { useEffect, useState } from 'react'
2
3// See: https://usehooks-ts.com/react-hook/use-isomorphic-layout-effect
4import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect'
5
6type ReturnType = [boolean, (locked: boolean) => void]
7
8function useLockedBody(initialLocked = false): ReturnType {
9 const [locked, setLocked] = useState(initialLocked)
10
11 // Do the side effect before render
12 useIsomorphicLayoutEffect(() => {
13 if (!locked) {
14 return
15 }
16
17 // Save initial body style
18 const originalOverflow = document.body.style.overflow
19 const originalPaddingRight = document.body.style.paddingRight
20
21 // Lock body scroll
22 document.body.style.overflow = 'hidden'
23
24 // Get the scrollBar width
25 const root = document.getElementById('___gatsby') // or root
26 const scrollBarWidth = root ? root.offsetWidth - root.scrollWidth : 0
27
28 // Avoid width reflow
29 if (scrollBarWidth) {
30 document.body.style.paddingRight = `${scrollBarWidth}px`
31 }
32
33 return () => {
34 document.body.style.overflow = originalOverflow
35
36 if (scrollBarWidth) {
37 document.body.style.paddingRight = originalPaddingRight
38 }
39 }
40 }, [locked])
41
42 // Update state if initialValue changes
43 useEffect(() => {
44 if (locked !== initialLocked) {
45 setLocked(initialLocked)
46 }
47 // eslint-disable-next-line react-hooks/exhaustive-deps
48 }, [initialLocked])
49
50 return [locked, setLocked]
51}
52
53export default useLockedBody

Usage

1import React, { CSSProperties, useState } from 'react'
2
3import { useLockedBody } from 'usehooks-ts'
4
5const fixedCenterStyle: CSSProperties = {
6 position: 'fixed',
7 top: '50%',
8 left: '50%',
9 transform: 'translate(-50%, -50%)',
10}
11
12const fakeScrollableStyle: CSSProperties = {
13 minHeight: '150vh',
14 background: 'linear-gradient(palegreen, palegoldenrod, palevioletred)',
15}
16
17// Example 1: useLockedBody as useState()
18export default function App() {
19 const [locked, setLocked] = useLockedBody()
20
21 const toggleLocked = () => {
22 setLocked(!locked)
23 }
24
25 return (
26 <div style={fakeScrollableStyle}>
27 <button style={fixedCenterStyle} onClick={toggleLocked}>
28 {locked ? 'unlock scroll' : 'lock scroll'}
29 </button>
30 </div>
31 )
32}
33
34// Example 2: useLockedBody with our custom state
35export function App2() {
36 const [locked, setLocked] = useState(false)
37
38 const toggleLocked = () => {
39 setLocked(!locked)
40 }
41
42 useLockedBody(locked)
43
44 return (
45 <div style={fakeScrollableStyle}>
46 <button style={fixedCenterStyle} onClick={toggleLocked}>
47 {locked ? 'unlock scroll' : 'lock scroll'}
48 </button>
49 </div>
50 )
51}

Edit on CodeSandbox

See a way to make this page better?
Edit there »