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

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