---
title: Core APIs
description: Choose and use the three React APIs for observing elements.
---

`react-intersection-observer` exports two hooks and one component. They take the same observer options and differ only in how they hand you the result.

| API | Use it when | What it returns |
| --- | --- | --- |
| `useInView` | Visibility affects rendered output. | A callback `ref`, `inView`, and the latest `entry`. |
| `useOnInView` | You need an effect without a hook-owned state update. | A callback `ref` and your `(inView, entry)` callback. |
| `<InView>` | Render props or a wrapper component fit the composition. | Render-prop fields, or a generated wrapper for plain children. |

:::note[Initial notification]
All three APIs suppress the observer's first `false` result. `initialInView` sets the starting state and changes how that first notification is handled. Every enter and leave transition after it reports both `true` and `false`.
:::

There is also a low-level [`observe`](#low-level-observe) function for code that already owns a DOM element.

## Common configuration choices

- [Load or reveal once](/api/configuration#stop-or-pause-observation) with `triggerOnce`.
- [Start work before the target reaches the viewport](/api/configuration#choose-when-it-counts-as-visible) with `rootMargin`.
- [Require a meaningful amount of content to be visible](/api/configuration#choose-when-it-counts-as-visible) with `threshold`.
- [Observe inside a scrolling container](/api/configuration#choose-where-to-observe) with `root`.
- Use [`useOnInView`](#useoninview) for analytics or prefetching that should not create a hook-owned re-render.

## `useInView`

Use `useInView` when visibility belongs in React state:

```tsx
import { useInView } from "react-intersection-observer";

export function ArticleSection() {
  const { ref, inView } = useInView();

  return <section ref={ref}>{inView ? "In view" : "Waiting"}</section>;
}
```

The hook supports both object and tuple destructuring:

```tsx
const { ref, inView, entry } = useInView();
const [ref, inView, entry] = useInView();
```

`entry` is `undefined` until the first accepted notification. After that it is the latest [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry), so read `intersectionRatio`, `boundingClientRect`, or `target` from it when the UI needs geometry.

### `onChange`

`useInView` can call `onChange` alongside its state update:

```tsx
const { ref, inView } = useInView({
  onChange(nextInView, entry) {
    console.log(entry.target, nextInView);
  },
});
```

Use `useOnInView` instead when the callback is the only result you need.

## `useOnInView`

`useOnInView` returns a ref callback and never updates component state. Use it for analytics, logging, prefetching, and anything else that should not cause a render:

```tsx
import { useOnInView } from "react-intersection-observer";

export function TrackedCard({ id }: { id: string }) {
  const ref = useOnInView(
    (inView, entry) => {
      if (inView) {
        analytics.track("card_visible", { id, target: entry.target });
      }
    },
    { threshold: 0.5, triggerOnce: true },
  );

  return <article ref={ref}>Card {id}</article>;
}
```

The callback receives `(inView, entry)`. `useOnInView` accepts the options that affect observation: `root`, `rootMargin`, `scrollMargin`, `threshold`, `triggerOnce`, `skip`, `trackVisibility`, and `delay`. It does not accept `onChange`, `initialInView`, or `fallbackInView`.

## `<InView>`

Use render props when the component should receive the ref and visibility state directly:

```tsx
import { InView } from "react-intersection-observer";

export function RevealCard() {
  return (
    <InView threshold={0.2} triggerOnce>
      {({ ref, inView, entry }) => (
        <article ref={ref} className={inView ? "card visible" : "card"}>
          <h2>Card</h2>
          <p>{entry ? "The observer has reported" : "Waiting"}</p>
        </article>
      )}
    </InView>
  );
}
```

The render prop receives `{ ref, inView, entry }`. Attach the `ref` to the element you want to observe.

### Plain children

Plain children always render. In this form `<InView>` creates a wrapper element, forwards any extra HTML props to it, and observes it:

```tsx
<InView as="section" className="article" onChange={handleChange}>
  <h2>Always-rendered content</h2>
</InView>
```

Use `as` to keep the generated wrapper semantic. This form does not forward refs. Use render props or `useInView` when you need direct control of the observed element, or a ref on a custom component.

## Low-level `observe`

When you already own a DOM element outside React rendering, use the low-level `observe` function. Keep the cleanup function it returns:

```ts
import { observe } from "react-intersection-observer";

const stop = observe(
  element,
  (inView, entry) => {
    console.log(inView, entry.intersectionRatio);
  },
  { threshold: 0.5 },
);

// Call this when the element is no longer relevant.
stop();
```

## Shared behavior

- `triggerOnce` stops observing after the first accepted `true` transition.
- `skip` disables observation while preserving the current state.
- When an observed node is removed and later replaced, the hook resets to `initialInView`, unless `triggerOnce` or `skip` prevents the reset.
- `entry` is `undefined` before the first accepted notification, and again after an observed node resets.

See [Configuration](/api/configuration) for every option and [Testing](/testing) for deterministic observer transitions.
