Skip to content
React Intersection Observer
Esc
navigateopen⌘Jpreview

Changelog

v10.0.2

January 15, 2026

Release
v10.0.0

October 28, 2025

Release

Based on the great work in #718 by @jantimon - This release adds the new useOnInView hook.

✨ New

  • useOnInView hook — a no-re-render alternative to useInView that delivers (inView, entry) to your callback while returning a ref you can attach to any element. Designed for tracking, analytics, and other side effect heavy workloads where state updates are unnecessary.
  • IntersectionChangeEffect / IntersectionEffectOptions types — exported helper types that describe the new hook’s callback and options surface.
  • Storybook playground + documentation — new story, README section, and JSDoc example demonstrating how to use useOnInView.

⚠️ Breaking Changes

  • useInView, useOnInView, and <InView> now ignore the browser’s initial inView === false emission for onChange events, so handlers only fire once a real visibility change occurs, while still reporting all subsequent enter/leave transitions.

🧪 Testing

  • Added a dedicated Vitest suite for useOnInView, covering thresholds, triggerOnce, skip toggling, merged refs, and multiple observers on the same node.

What’s Changed

New Contributors

Full Changelog: https://github.com/thebuilder/react-intersection-observer/compare/v9.16.0...v10.0.0

v9.15.1

January 21, 2025

Release

9.15.1 (2025-01-21)

Bug Fixes

Fixes and improvements to react-intersection-observer/test-utils

  • always recreate the IntersectionObserver mock (c720914)
  • improve test lib detection (1858e43)
  • use isMockFunction to detect if the IntersectionObserver is being mocked (189133f)
v9.14.0

December 14, 2024

Release

9.14.0 (2024-12-14)

Features

  • support React 19 (8bafe25)
  • Drop support for React 15/16. The types are no longer compatible with later version of React, since they have dropped the deprecated types.
v9.12.0

July 11, 2024

Release

9.12.0 (2024-07-11)

Features

  • upgrade to Vitest 2 (#690) (054adf4)
    • Upgrade to Vitest 2
    • Add basic browser mode test, that runs with an unmocked IntersectionObserver
    • Types cleanup in test-utils, getting rid of @types/jest (replaced with vitest)
    • Move test mocking setup from beforeEach to beforeAll
v9.7.0

February 5, 2024

Release

9.7.0 (2024-02-05)

This release switches from ESLint and Prettier to Biome.js, as a way of cutting down on dev dependencies and configuration.

Features

  • migrate to Biome.js formatting (529b46f)
  • switch to Biome.js linting (2b3642f)

Reverts

  • preserve the commonjs in tsconfig.test.json (61bd06b)
v9.5.0

June 17, 2023

Release

9.5.0 (2023-06-17)

Breaking changes

This release switches the build tool to tsup, in order for the package to support the use client directive. Hopefully it shouldn’t have an impact on usage, but changing build output, always carries a slight risk of some tooling breaking.

  • Changes the export file names. If you relied on importing files directly, then you’ll need to change the imports.
  • Drops bundling the “umd” version. If anyone was relying on this, let me know.

Features

  • support “use client” with tsup (db2254b)
  • switch to tsup build (9bd69f5)
v9.4.4

June 1, 2023

Release

9.4.4 (2023-06-01)

Bug Fixes

  • correct the error message that happens if IntersectionObserver is not mocked (f7da436)
v9.2.0

May 28, 2022

Release

9.2.0 (2022-05-28)

Features

  • Switch to vitest and storybook vite (#549) (af0440f)
  • Support both Jest and Vitest in test-utils
  • Improve readme section on testing
v9.1.0

April 25, 2022

Release

9.1.0 (2022-04-25)

Features

  • support onChange on useInView (ba8d059)
  const { ref } = useInView({
    onChange: (inView) => {
      if (inView) {
        dataLayer.push('Section shown'); // Here's a GTM dataLayer push
      }
    },
  });
v9.0.0

April 21, 2022

Release

9.0.0 (2022-04-21)

This release is mainly to fix an issue with the exports not being properly tree-shaken.

Features

Size of exports

After tree shaking, this is the current size impact of the exports, as reported by size-limit.

  • InView - 1.6 kB with all dependencies, minified and gzipped
  • useInView - 1.13 kB with all dependencies, minified and gzipped
  • observe - 835 B with all dependencies, minified and gzipped

BREAKING CHANGES

  • Removed the default export of the InView component. Use the named InView import instead
-import InView from 'react-intersection-observer
+import { InView } from 'react-intersection-observer
v8.34.0

April 12, 2022

Release

8.34.0 (2022-04-12)

Features

  • upgrade to React 18 (#538) (98bb83f)
  • Upgrade Storybook and other dev dependencies.

Breaking changes

  • This removes the old tag prop from <InView>, that was deprecated by as some years ago. If you haven’t changed it, this could break your build. The fix would be to replace all instances of tag with as.
  • Changed the type of as from the old ReactType to ElementType. This would only be a problem if you are still using the old version of the React Types.
v8.33.0

December 9, 2021

Release

8.33.0 (2021-12-09)

Features

  • add support for fallbackInView (#521) (aa50422) - Closes #495

This is solution for #495 that adds support for a fallbackInView value.

You can set the fallback globally:

import { defaultFallbackInView } from 'react-intersection-observer';
defaultFallbackInView(true); // or 'false'

You can also define the fallback locally on useInView or <InView> as an option. This will override the global fallback value.

import React from 'react';
import { useInView } from 'react-intersection-observer';

const Component = () => {
  const { ref, inView, entry } = useInView({
    fallbackInView: true,
  });

  return (
    <div ref={ref}>
      <h2>{`Header inside viewport ${inView}.`}</h2>
    </div>
  );
};
v8.28.1

September 7, 2020

Release

8.28.1 (2020-09-07)

Bug Fixes

  • There was an issue with the CJS and UMD files generated by microbundle, not matching filename in package.json
v8.28.0

September 7, 2020

Release

8.28.0 (2020-09-07)

Features

Details

This is a rewrite of the internal handling of IntersectionObservers, alongside general refactoring and alignment of the code.

  • Breaking change: Remove default export for the InView component
  • Remove outdated fixes for the initial IntersectionObserver browser implementation. These were needed 3 years ago, to ensure all browsers acted according to the spec
  • Support multiple observers on the same element. Closes #340
  • Rewrite tests, so they use test-utils to properly mock the IntersectionObserver instances
  • Switch to microbundle for compiling
  • Add initial IntersectionObserver v2 support. Closes #368

IntersectionObserver v2 🧪

The new v2 implementation of IntersectionObserver extends the original API, so you can track if the element is covered by another element or has filters applied to it. Useful for blocking clickjacking attempts or tracking ad exposure.

To use it, you’ll need to add the new trackVisibility and delay options. When you get the entry back, you can then monitor if isVisible is true.

const TrackVisible = () => {
  const { ref, entry } = useInView({ trackVisibility: true, delay: 100 });
  return <div ref={ref}>{entry?.isVisible}</div>;
};

This is still a very new addition, so check caniuse for current browser support. If trackVisibility has been set, and the current browser doesn’t support it, a fallback has been added to always report isVisible as true.

It’s not added to the TypeScript lib.d.ts file yet, so you will also have to extend the IntersectionObserverEntry with the isVisible boolean.

v8.24.2

September 9, 2019

Release
  • fix: don’t add onChange prop to the plain children container (#272) 4d88ea2
  • Fix recipe for triggering animations (#271) 31a890b
  • chore(package): update babel-eslint to version 10.0.3 (#268) 4af02e7
  • chore: upgrade dependencies b20abcc
  • chore(package): update @typescript-eslint/eslint-plugin to version 2.0.0 (#261) a5061a5
  • Changed typo const supportsNativeLoading to supportsLazyLoading (#265) fdc4e63
  • Add codesandbox example 3fe0710
  • Merge remote-tracking branch ‘origin/master’ e037bdc
  • docs: update the recipes c6d1f48

https://github.com/thebuilder/react-intersection-observer/compare/v8.24.1...v8.24.2

v8.24.1

July 15, 2019

Release
  • chore: upgrade dependencies 4734187
  • refactor: switch to useCallback for setting node ref b31487b
  • build(deps): bump lodash-es from 4.17.11 to 4.17.14 (#253) 828cfa9
  • Add note about constant threshold array (#251) 2f65d87
  • chore: upgrade dependencies 7961292
  • Update eslint-plugin-import to the latest version 🚀 (#245) 8a77732
  • Update eslint to the latest version 🚀 (#244) f5fe897
  • Update babel-eslint to the latest version 🚀 (#242) 7319667
  • Minor JS typos in hook recipes (#243) b473795
  • Minor JS typos in hook recipes (#243) f71d5c0
  • Update babel-eslint to the latest version 🚀 (#242) c627551
  • Minor JS typos in hook recipes (#243) 5f40a96
  • Minor JS typos in hook recipes (#243) d06da8c
  • readme: fix typo 0ca244a
  • build: remove np from devDependencies 6fa9cc1
  • build: add an improved release script 5806034
  • refactor: make types for Observer readonly cb7e9f6
  • build: upgrade the build command daf6805
  • chore: upgrade dependencies, including switching to the @testing-library/react namespace and TypeScript 3.51 e268f18
  • Update Recipes.md 3029aa8
  • docs: fix typos c91f718
  • Update Recipes.md e1fdeff
  • Merge remote-tracking branch ‘origin/master’ 197ccce
  • docs: update the documentation, fixing typos and add recipes fe6d30f
  • chore(package): update rollup-plugin-terser to version 5.0.0 (#230) 755e92e
  • Update eslint-plugin-import to the latest version 🚀 (#234) 5ec08c5
  • Update rollup-plugin-commonjs to the latest version 🚀 (#227) e71f220
  • Update intersection-observer to the latest version 🚀 (#226) 6593532
  • Update package.json 9b743f9
  • don’t use yarn for np. Might not work correctly with contents 0a1b726
  • Update package.json 1a3f5d7

https://github.com/thebuilder/react-intersection-observer/compare/v8.23.1...v8.24.0

Restructure package contents

April 29, 2019

Release

This release restructures how the project is published to npm. Instead of publishing the entire project, it just publishes the content of the dist directory. This is only a breaking change, if you were importing files from the src or dist directory before.

If you do run into an issue with imports, it should be fixed by importing directly from react-intersection-observer.

TypeScript Tweaks

March 25, 2019

Release

This release tweaks the Typescript typings for the PlainChildren version, so it should be more generic.

Rework some internals

March 11, 2019

Release

Potential breaking change #195

Change for the <InView>

Previously, the initial callback would trigger a state update, even if the element was still outside the viewport. This update changes the behavior, so it won’t trigger new state update before the element enters the viewport.

If you used the IntersectionObserverEntry during that first callback, it will no longer work. The use case for this, is most likely to determine if the element started outside the viewport.

Workaround

As a workaround, you can use the onChange callback, since this will still be called everytime.

Testing utils

March 6, 2019

Release

This releases is focused on helping you when writing tests.

In order to write meaningful tests, the IntersectionObserver needs to be mocked. If you are writing your tests in Jest, you can use the included test-utils.js. It mocks the IntersectionObserver, and includes a few methods to assist with faking the inView state.

test-utils.js

Import the methods from react-intersection-observer/test-utils.

mockAllIsIntersecting(isIntersecting:boolean)
Set the isIntersecting on all current IntersectionObserver instances.

mockIsIntersecting(element:Element, isIntersecting:boolean)
Set the isIntersecting for the IntersectionObserver of a specific element.

intersectionMockInstance(element:Element): IntersectionObserver
Call the intersectionMockInstance method with an element, to get the (mocked) IntersectionObserver instance. You can use this to spy on the observe and unobserve methods.

Test Example

import React from 'react'
import { render } from 'react-testing-library'
import { useInView } from 'react-intersection-observer'
import { mockAllIsIntersecting } from 'react-intersection-observer/test-utils'

const HookComponent = ({ options }) => {
  const [ref, inView] = useInView(options)
  return <div ref={ref}>{inView.toString()}</div>
}

test('should create a hook inView', () => {
  const { getByText } = render(<HookComponent />)
  mockAllIsIntersecting(true)
  getByText('true')
})
Rewritten Hooks and tests

February 10, 2019

Release

So now that Hooks have been out in the wild for a week, a few issues are starting to pop up. Especially relating to how refs are handled (#162). To fix this, the current API needs to be changed.

New useInView API:

const [ref, inView, entry] = useInView(options)

If you are already using the hook, then you will need to update your code, by changing:

const Component = () => {
  const ref = useRef()
  const inView = useInView(ref, {
    threshold: 0,
  })

  return (
    <div ref={ref}>
      ...
    </div>
  )
}

Into:

const Component = () => {
  const [ref, inView] = useInView({
    threshold: 0,
  })

  return (
    <div ref={ref}>
      ...
    </div>
  )
}

Removed

  • The useIntersectionObserver hook was removed in favor of the useInView hook.

Tests

Tests have been rewritten using react-testing-library. Before they were messing with the internals of the components to fake updates. It also just works with the new hooks.

TypeScript edition! 🎉

January 30, 2019

Release

I decided to switch the project from Flow to TypeScript. This should ensure the TypeScript definitions match the actual implementation, without the need to manually sync the index.d.tsfile.

In the process, i’ve rewritten some of the internals, but it shouldn’t affect the actual API.

A documentation site has also been created using docz: https://react-intersection-observer.now.sh

Breaking changes

  • The deprecated render method has been removed - Make sure you use children instead.
  • Instead of returning just intersectionRatio, you now get entry that contains the entire IntersectionObserverEntry element. If you’re relying on intersectionRatio, you should change your code to entry.intersectionRatio.
  • rootId has been removed - An idea for each unique root is now auto generated. This always felt like temporary solution, until i implemented a smarter way.
  • Flow types have been removed.
Intersection Ratio

January 8, 2019

Release

This release exposes the intersectionRatio, giving you a bit more insight into what triggered an update.

import { InView } from 'react-intersection-observer'

const Component = () => (
  <InView>
    {({ inView, ref, intersectionRatio }) => (
      <div ref={ref}>
        <h2>{`Header inside viewport ${inView} at ${intersectionRatio}`}</h2>
      </div>
    )}
  </InView>
)

export default Component

See #123

Hooks!

November 28, 2018

Release

This release enables support for the useInView(ref, options) hook. The library will work fine with older versions of React, as long as you don’t use the execute the hook.

import { useRef } from 'react'
import { useInView } from 'react-intersection-observer'

const Component = () => {
  const ref = useRef()
  const inView = useInView(ref, {
    /* Optional options */
    threshold: 0,
  })

  return (
    <div ref={ref}>
      <h2>{`Header inside viewport ${inView}.`}</h2>
    </div>
  )
}

The release also refactors the Rollup build, which should result in a more optimized bundle.

Babel 7

September 4, 2018

Release

This release updates Babel and Rollup, and publishes the new output files.

Keep plain children

June 28, 2018

Release

Due to popular demand (#91), I’ll keep support for rendering plain children inside the <Observer />.

This release just removes the deprecation flag. You don’t need to change anything.

Simpler times

June 26, 2018

Release

Looking at how i’m currently using react-intersection-observer, and how render props have been adapted by React, this release moves all rendering to to the children prop.

The way to use the component is to forward the ref to your outer element, giving you full control over the DOM.

import Observer from 'react-intersection-observer'

const Component = () => (
  <Observer>
    {({ inView, ref }) => (
      <div ref={ref}>
        <h2>{`Header inside viewport ${inView}.`}</h2>
      </div>
    )}
  </Observer>
)

export default Component

⚠️ Breaking changes

  • Deprecated renderprop
  • Deprecated tag prop
  • Deprecated child as React.Node

They will still work for now, but you’ll get a deprecation warning unless NODE_ENV is production.

If you are currently using render, you can move the method directly to children.

Keep on rolling

May 20, 2018

Release

Switch the build system to Rollup, so the package now contains an umd, CommonJS and ES optimized build. 🎉

This shouldn’t change anything if you are already using the package, but if you imported the files in dist directly then that won’t work anymore

Render Props with refs

May 14, 2018

Release

No more wrapping div element! render now gives complete control over the rendering, making it easier to use while keeping HTML semantic

import Observer from 'react-intersection-observer'

const Component = () => (
  <Observer
    render={({ inView, ref }) => (
      <div ref={ref}>
        <h2>{`Header inside viewport ${inView}.`}</h2>
      </div>
    )}
  />
)

export default Component

⚠️ Breaking change

  • Removed innerRef
  • Changed render callback arguments

This release breaks the render prop, so it differs from the child as function method. If you used the render method before, and you were happy with, you can move the function to children. Otherwise, you’ll need to handle the new ref and assign it to a HTMLElement.

Old way

<Observer render={inView => <h2>Inview {inView}</h2>} />

New way

<Observer render={({inView, ref}) => <div ref=ref><h2>Inview {inView}</h2></div>} />
v3.0.1

January 4, 2018

Release

This fixes #53

Render Prop Pattern

December 21, 2017

Release

The render prop pattern recently became a popular replacement for the child as function pattern.

Previously the render prop on react-intersection-observer, was handled differently from children so it only rendered when the element was inside the viewport.

⚠️ Breaking change

If you were using the render prop before, you need to handle when to render the content.

Old way

<Observer render={() => <div>Only rendered when in viewport</div>} />

New way

<Observer render={inView => inView ? <div>Only rendered when in viewport</div> : null} />