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
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 »