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

Usage

1import { 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 »