Skip to content

Instantly share code, notes, and snippets.

@gragland
Last active March 5, 2023 21:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gragland/d1175eb983772b077cb17ae0841c5329 to your computer and use it in GitHub Desktop.
Save gragland/d1175eb983772b077cb17ae0841c5329 to your computer and use it in GitHub Desktop.
import { useState, useEffect, useRef } from 'react';
// Usage
function App() {
// Ref for the element that we want to detect whether on screen
const ref = useRef();
// Call the hook passing in ref and root margin
// In this case it would only be considered onScreen if more ...
// ... than 300px of element is visible.
const onScreen = useOnScreen(ref, '-300px');
return (
<div>
<div style={{ height: '100vh' }}>
<h1>Scroll down to next section 👇</h1>
</div>
<div
ref={ref}
style={{
height: '100vh',
backgroundColor: onScreen ? '#23cebd' : '#efefef'
}}
>
{onScreen ? (
<div>
<h1>Hey I'm on the screen</h1>
<img src="https://i.giphy.com/media/ASd0Ukj0y3qMM/giphy.gif" />
</div>
) : (
<h1>Scroll down 300px from the top of this section 👇</h1>
)}
</div>
</div>
);
}
// Hook
function useOnScreen(ref, rootMargin = '0px') {
// State and setter for storing whether element is visible
const [isIntersecting, setIntersecting] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
// Update our state when observer callback fires
setIntersecting(entry.isIntersecting);
},
{
rootMargin
}
);
if (ref.current) {
observer.observe(ref.current);
}
return () => {
observer.unobserve(ref.current);
};
}, []); // Empty array ensures that effect is only run on mount and unmount
return isIntersecting;
}
@gragland
Copy link
Author

@mattfysh and @Andarist: Good points! So I'm thinking it would make sense to pass ref.current in input array so that effect is recalled if it changes and then wrap entire effect function body in if (ref.current) { ... } so that it does nothing if ref.current is falsy. Thoughts?

@QuentinRoy
Copy link

I agree. I do believe ref.current should be passed as argument of useEffect line 58. Currently if the ref changes, nothing happens and the previous is leaked.

@QuentinRoy
Copy link

You may also consider returning the ref from the hook instead of passing it as argument, c.f. https://usehooks.com/useHover/.

@manavm1990
Copy link

observer.unobserve(ref.current);

In the useEffect, we should keep a reference to the ref.current like this: const currentRef = ref.current.

Then do: observer.unobserve(currentRef);

In doing so, we will ensure that ref.current doesn't end up changing before this cleanup is run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment