Last active
October 11, 2019 17:49
-
-
Save gragland/f50690d2724aec1bd513de8596dcd9b9 to your computer and use it in GitHub Desktop.
React Hook recipe from https://usehooks.com. Demo: https://codesandbox.io/s/yvkol51m81
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useLayoutEffect } from 'react'; | |
// Usage | |
function App(){ | |
// State for our modal | |
const [modalOpen, setModalOpen] = useState(false); | |
return ( | |
<div> | |
<button onClick={() => setModalOpen(true)}>Show Modal</button> | |
<Content /> | |
{modalOpen && ( | |
<Modal | |
title="Try scrolling" | |
content="I bet you you can't! Muahahaha 😈" | |
onClose={() => setModalOpen(false)} | |
/> | |
)} | |
</div> | |
); | |
} | |
function Modal({ title, content, onClose }){ | |
// Call hook to lock body scroll | |
useLockBodyScroll(); | |
return ( | |
<div className="modal-overlay" onClick={onClose}> | |
<div className="modal"> | |
<h2>{title}</h2> | |
<p>{content}</p> | |
</div> | |
</div> | |
); | |
} | |
// Hook | |
function useLockBodyScroll() { | |
useLayoutEffect(() => { | |
// Get original value of body overflow | |
const originalStyle = window.getComputedStyle(document.body).overflow; | |
// Prevent scrolling on mount | |
document.body.style.overflow = 'hidden'; | |
// Re-enable scrolling when component unmounts | |
return () => document.body.style.overflow = originalStyle; | |
}, []); // Empty array ensures effect is only run on mount and unmount | |
} |
When using it in TypeScript, it complains that the first param doesn't match EffectCallback
, since it's returning () => string
.
Changing return () => (document.body.style.overflow = 'visible')
into return () => {document.body.style.overflow = 'visible'}
fixes this.
This currently won't work for iOS devices. You could rewrite it like this
// Create ref to bind to element that should prevent scrolling
const ref = useRef(null);
useEffect(() => {
// Add/Remove listeners for touchmove -> finding touchmove better, as it doesn't prevent other touch events like clicking links
// The checks for ref && ref.current made it more TS friendly for me
if (ref && ref.current) {
ref.current.addEventListener('touchmove', e => e.preventDefault());
}
return () => {
if (ref && ref.current) {
ref.current.removeEventListener('touchmove', e => e.preventDefault());
}
};
}, []);
useLayoutEffect(() => {
// Get original body overflow
const originalStyle = window.getComputedStyle(document.body).overflow;
// Prevent scrolling on mount
document.body.style.overflow = 'hidden';
// Re-enable scrolling when component unmounts
return () => {
return (document.body.style.overflow = originalStyle);
};
}, []); // Empty array ensures effect is only run on mount and unmount
// Return the ref, to bind it to the element;
return ref;
}````
And by binding the ref to the element that locks the scrolling, iOS devices will also respect the scroll lock
I took a shot at modifying this to make it work:
- On iOS
- When multiple "instances" of
lockBodyScroll
(modals in this case) are active
It seems to be working with my changes in my sandbox fork here: https://codesandbox.io/s/uselockbodyscroll-example-mq9rm.
Or test it on your iOS device here: https://mq9rm.csb.app/
But I'm pretty new to custom hooks (and programming, really) so I'm open to suggestions/critique 👍🏼
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Why does it use
useLayoutEffect
? Seems unnecessary to me since we only use it on mount and unmount.