React Intersection Observer
react-intersection-observer is a comprehensive React library that provides hooks and components for efficiently leveraging the browser's Intersection Observer API. It allows developers to monitor when a React component enters or leaves the viewport, enabling features like lazy loading images, implementing infinite scrolling, or triggering animations based on visibility. The current stable version is `10.0.3`, and the project maintains an active release cadence, frequently pushing updates and bug fixes. Key differentiators include its dual API (hooks like `useInView` and `useOnInView`, plus a `<InView>` component), optimized performance through observer instance reuse, native API alignment, robust TypeScript support, and a tiny bundle size (around ~1.15kB for `useInView`). The `useOnInView` hook, introduced in v10, offers a no-re-render alternative for side-effect-heavy workloads like analytics tracking, further enhancing its utility. It also includes comprehensive test utilities for Jest and Vitest.
Common errors
-
TypeError: (0 , react_intersection_observer__WEBPACK_IMPORTED_MODULE_2__.useInView) is not a function
cause This typically occurs when trying to use named ESM exports with a CommonJS `require` call or when a bundler's configuration incorrectly transpiles modules.fixEnsure you are using ESM `import` statements: `import { useInView } from 'react-intersection-observer';`. If using TypeScript with CommonJS output, verify your `tsconfig.json` `module` option (e.g., `"ESNext"` or `"Node16"`) and your bundler's settings (e.g., Webpack, Rollup, Vite) support ESM correctly. -
React Hook 'useInView' cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
cause You are attempting to call `useInView` (or any other React Hook) inside a regular JavaScript function, an event handler, or a conditional block, violating the Rules of Hooks.fixMove the `useInView` call directly into the body of your React functional component or into another custom hook. Hooks must always be called at the top level of a component or custom hook. -
Element not entering 'inView' state despite being visible in the viewport.
cause This often happens due to incorrect `threshold` values, `rootMargin` settings, or the `ref` not being correctly attached to the target DOM element.fixDouble-check your `threshold` (e.g., `threshold: 0` means any part visible, `threshold: 1` means entirely visible) and `rootMargin` (e.g., `rootMargin: '10px 0px'` extends the root bounds). Ensure the `ref` returned by `useInView` is correctly assigned to the DOM element you intend to observe: `<div ref={ref}>...</div>`.
Warnings
- breaking Support for React 15 and 16 has been officially dropped in version 9.14.0. Projects using older React versions will need to remain on a prior major version of `react-intersection-observer` or upgrade their React dependency.
- gotcha The `options` object passed to `useInView` should be stable (e.g., memoized or defined outside the component) to prevent unnecessary re-creation of IntersectionObserver instances, which can lead to performance issues or unexpected behavior.
- gotcha The `triggerOnce` option, when combined with merged refs, might skip the initial callback in some edge cases. This was addressed in v10.0.3.
- gotcha By design, the first `false` notification from the underlying Intersection Observer is ignored by `useInView` so that handlers only run after a 'real' visibility change. This means your `onChange` callback or `inView` state might not immediately reflect `false` on initial render if the element is not in view.
Install
-
npm install react-intersection-observer -
yarn add react-intersection-observer -
pnpm add react-intersection-observer
Imports
- useInView
const useInView = require('react-intersection-observer').useInView;import { useInView } from 'react-intersection-observer'; - InView
import InView from 'react-intersection-observer';
import { InView } from 'react-intersection-observer'; - useOnInView
import { useOnInView } from 'react-intersection-observer'; - IntersectionObserverEntry
import type { IntersectionObserverEntry } from 'react-intersection-observer';
Quickstart
import React from 'react';
import { useInView } from 'react-intersection-observer';
const LazyLoadedImage = ({ src, alt }) => {
const { ref, inView } = useInView({
triggerOnce: true, // Only trigger once when it enters the viewport
threshold: 0.1, // Trigger when 10% of the element is visible
});
return (
<div ref={ref} style={{ height: '300px', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f0f0f0' }}>
{inView ? (
<img src={src} alt={alt} style={{ maxWidth: '100%', maxHeight: '100%' }} />
) : (
<p style={{ color: '#888' }}>Loading image...</p>
)}
</div>
);
};
const App = () => {
return (
<div>
<div style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<h1>Scroll Down to See Image</h1>
</div>
<LazyLoadedImage src="https://via.placeholder.com/600x300.png?text=Intersection+Observer" alt="Placeholder Image" />
<div style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<h2>End of Page</h2>
</div>
</div>
);
};
export default App;