react-intersection-observer: Hooks, Render Props and Plain Children for Viewport Detection
React implementation of the Intersection Observer API to tell you when an element enters or leaves the viewport.
At a glance
- What is it?
- A React wrapper around the browser's Intersection Observer API, with three APIs, shared observers, and a documented first-notification quirk. Here is what it does, how to install it, and where it stops being the right tool.
- Who is it for?
- Adopt it when you want viewport detection expressed as React state or a callback and you are happy with the native IntersectionObserver options model. Do not adopt it if you need a ref forwarded through plain children, or if you are targeting an environment without IntersectionObserver and cannot supply a fallback.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 23 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What react-intersection-observer solves, and who it is aimed at
The browser's Intersection Observer API already answers the question "is this element visible?" without scroll listeners. What it does not give you is a React-shaped answer. You create an observer, keep a DOM node in a ref, wire up a callback, and clean all of it up when the component unmounts. For one element that is tedious. For a list of fifty it is a source of leaks.
This package is a React implementation of that API. The README lists its intended uses plainly: scroll animations, lazy loading, impression tracking, and infinite scroll. Those four cover most of the reason a React developer reaches for viewport detection at all. The audience is application developers who want the visibility state to live inside the component that renders the element, rather than in a separate observer registry.
It is not a general-purpose animation library and it does not polyfill the browser API. It maps React component lifecycles onto IntersectionObserver instances and gets out of the way.
Three APIs over one observer: useInView, useOnInView and InView
The package exposes the same underlying mechanism through three surfaces, and the choice between them is mostly about where state should live.
`useInView` returns a ref, an `inView` boolean, and the current `IntersectionObserverEntry`. Because it is a hook, a visibility change re-renders the component. `useOnInView` takes a callback and returns a ref; the README states it holds no state, so a visibility change never triggers a render. That distinction matters for impression tracking, where you want to record an event without re-rendering a list item. The callback receives the boolean first and the entry second, matching the `onChange` signature that `useInView` accepts. It accepts every option `useInView` does except `onChange`, `initialInView` and `fallbackInView`.
`<InView>` covers the component cases. With a function child it behaves like a render prop, handing you `inView`, `ref` and `entry`. With plain children it creates the wrapping element itself and you supply `onChange` to observe state. Extra props go to the HTML element, so `className` and `style` work as expected, and the README suggests changing `as` to keep the output semantic.
The efficiency claim in the README is that observers with matching options are reused, so watching many elements stays cheap. That is the design detail worth understanding: the cost of this library is not per element, it is per distinct option set. Ten thousand elements with `threshold: 0` share one observer. Ten thousand elements each with a different threshold do not.
Installing react-intersection-observer and a first useInView example
Installation is a single npm command. The README also links a StackBlitz playground if you would rather not set up a project to try it.
npm install react-intersection-observer --saveOnce installed, the smallest useful example is the hook. The README's own snippet destructures the return value and attaches the ref to a div.
import React from "react";
import { useInView } from "react-intersection-observer";
const Component = () => {
const { ref, inView, entry } = useInView({
/* Optional options */
threshold: 0,
});
return (
<div ref={ref}>
<h2>{`Header inside viewport ${inView}.`}</h2>
</div>
);
};Assign the ref to the element you want to watch, and the hook reports the status. The README notes that both object destructuring and array destructuring work, the latter letting you rename the fields.
One behaviour to internalise before you write any logic on top: the README states that the first `false` notification from the underlying IntersectionObserver is ignored, so handlers only run after a real visibility change. The same note appears for `<InView>` and for `useOnInView`. If you are counting impressions, that means you will not receive a spurious "not visible" event on mount, which is usually what you want. If you were relying on that initial callback to establish a baseline, you will need another source for it.
The options map straight to `IntersectionObserverInit`, so anything you know about the native API transfers without translation. For a callback-driven variant that avoids re-renders, `useOnInView` takes the same options object as its second argument.
Where the API pushes back: refs, fallbacks and plain children
The README is unusually direct about a limitation that most wrapper libraries bury. When you render a plain child, the component does not forward refs. If you need a ref to the HTML element, you have to use the render props version instead. That is a real constraint on composition, and it is the kind of thing you discover halfway through building a component that needs both the visibility state and a measurement of the node.
`fallbackInView` exists as an option on `useInView`, which implies the library has a story for environments without IntersectionObserver, but the README excerpt does not document what that story is. Treat it as something to check in the repository before you depend on it for older browsers or non-browser renderers.
There is also a subtler cost in `useInView`: it is stateful, so every enter and leave transition re-renders the component holding the hook. For a single hero section that is irrelevant. For a virtualised list where each row uses `useInView`, you are trading observer bookkeeping for render work, and `useOnInView` is the escape hatch the README points at. That is a design trade-off, not a defect, but it is the one that decides which of the three APIs you pick.
Finally, the shared-observer optimisation only pays off when options match. An application that computes thresholds dynamically per element will end up with an observer per element and lose the benefit the README advertises.
react-intersection-observer versus writing the IntersectionObserver yourself
The honest alternative is not another library. It is a `useEffect` that constructs an `IntersectionObserver`, observes a ref, and disconnects on cleanup. That is roughly fifteen lines, it has no dependency, and it gives you exactly the native API with no abstraction in between.
The difference in approach is what you get for those fifteen lines. The package adds observer sharing across components with matching options, which a hand-rolled hook will not do unless you build a registry yourself. It adds three consistent surfaces so a codebase can use a hook in one place and a render prop in another without two mental models. It adds a documented testing story, with mocks for Jest and Vitest stated in the README. And it ships TypeScript types with the package, plus a tree-shakeable build, which the README quantifies at around 1.15kB gzipped for `useInView` and 1.6kB for `<InView>`.
What you give up is control over the lifecycle and the ability to reason about one observer per component. If your application has a single element to watch and no test suite that needs mocking, the hand-rolled effect is defensible and the dependency is not earning its place. If you have many elements, mixed rendering patterns, and tests, the shared observers and the mock support are the reason the package exists.
Maintenance, licence and the upgrade surface
The repository is not archived, and the last push was on 2026-09-11. The most recent release listed is v11.0.1 from 2026-08-26, following v11.0.0 on 2026-07-30 and v10.1.0 on 2026-07-10. That is a major version bump within the last two months of the recorded history, which is the fact that should drive your upgrade planning more than anything else here.
A major version means the API surface can change. The README documents three entry points (`useInView`, `useOnInView`, `<InView>`) and a set of options that map to `IntersectionObserverInit`, plus three options that `useOnInView` explicitly does not accept. Those are the names to diff against your installed version before upgrading. The package is written in TypeScript and ships its types, so a type error at build time is your first signal.
Licensing is MIT. That is permissive and compatible with commercial use, but this is a description of the licence identifier, not legal advice. If your organisation has specific obligations around attribution or notice files, check the LICENSE file in the repository and your own policy.
The repository is a pnpm workspace with Turbo, separate `apps/` and `packages/` directories, and a Storybook plus docs app. That layout tells you the maintenance burden is not carried by a single published file. For a consumer it means the published package is built from a monorepo, so issues and pull requests may reference workspace-level scripts rather than the package directory.
Editorial conclusion
Adopt it when you want viewport detection expressed as React state or a callback and you are happy with the native IntersectionObserver options model. Do not adopt it if you need a ref forwarded through plain children, or if you are targeting an environment without IntersectionObserver and cannot supply a fallback. Before shipping, verify two things: that your test setup mocks the observer (the README states Jest and Vitest are supported), and whether the skipped first false notification changes your tracking logic, since the README documents that behaviour explicitly.
Frequently asked questions
How do I use the Intersection Observer in React?
Import useInView from react-intersection-observer, call it with an optional options object, and assign the returned ref to the DOM element you want to watch. The hook then reports the inView status and the current entry.
What does an intersection observer do?
In this package it tells you when an element enters or leaves the viewport. The README lists scroll animations, lazy loading, impression tracking and infinite scroll as the intended uses.
How to install react intersection observer?
Run npm install react-intersection-observer --save. The README also links a StackBlitz playground if you want to try it without setting up a project.
What is react intersection observer?
It is a React implementation of the browser's Intersection Observer API that reports when an element enters or leaves the viewport. It ships hooks, render props and plain children, and is written in TypeScript.
What is the difference between an intersection observer and a scroll event?
The package is described as a React implementation of the Intersection Observer API, which tells you when an element enters or leaves the viewport. The README does not compare it against scroll event listeners, so any performance comparison would have to come from the native API documentation rather than this project.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/thebuilder-react-intersection-observer)