Hooks or component
useInView for state, useOnInView for effects, and <InView> for render props or a wrapper.
Reveal on scroll, lazy-load images, track impressions, build infinite lists. A tiny, fully-typed React adapter for the Intersection Observer API, in about a kilobyte.
$ npm i react-intersection-observerEvery card below waited off-screen and animated in as it crossed the threshold, with the same useInView you would ship. It fires once and then leaves the DOM alone.
useInView for state, useOnInView for effects, and <InView> for render props or a wrapper.
Around 1 kB gzipped per API. Tree-shakeable, so you ship only what you import.
The same threshold, rootMargin, and root options you already know from IntersectionObserver.
Elements with identical options reuse one observer, so thousands of targets stay cheap.
Written in TypeScript. Options, return values, and entries are typed, with no extra @types.
A drop-in mock for the Intersection Observer keeps Vitest and Jest suites deterministic.
useInView({ triggerOnce: true })drives every reveal on this page.Pick the shape that fits your component. The panel follows whichever you are reading. That is useInView doing scrollspy, right here. Tap a card to pin one.
const { ref, inView } = useInView({ threshold: 0.5,});<section ref={ref}> {inView ? "In view" : "Waiting"}</section>const { ref, inView } = useInView({ threshold: 0.5,});<section ref={ref}> {inView ? "In view" : "Waiting"}</section>const ref = useOnInView( (inView, entry) => { track("seen", entry.target); }, { threshold: 1, triggerOnce: true },);<InView as="div" threshold={0.2} triggerOnce> {({ ref, inView }) => ( <div ref={ref}> {inView ? "Loaded" : "Placeholder"} </div> )}</InView>Every tile below reserves its space, then mounts its <img> once the observer says it is within 200px of the viewport. Nothing above the fold waits on artwork that is still three screens away.
loading="lazy" instead. Reach for the observer when you need the preload margin, a placeholder, or a transition.useOnInView runs your callback without a hook-owned re-render. It is the right tool for impressions, prefetching, and logging. Each tile below logs itself the first time it is fully visible.
Change the threshold and scroll the custom root. Same hook, wired to live controls and a live readout.
Install, attach a ref, and read inView. Thresholds, roots, and margins are there when you need them.
$ npm i react-intersection-observerTesting? The package ships a mock for the Intersection Observer so your suites stay deterministic.