---
id: "packages/active-virtual/react"
title: "React"
description: "useVirtualAxis - options, the controller, measuring items, and two axes on one container."
url: "/packages/active-virtual/react"
locale: "en"
revision: "7950d88ea748b8c4"
---

# React

## The hook

```tsx
const {projection, controller, itemRef, isRendered} = useVirtualAxis({
  count: rows.length, // or keys: rows.map((row) => row.id)
  estimate: 36,
  container: scrollElement,
});
```

| Returned | What it is |
| --- | --- |
| `projection` | what to render now: `items` (each with `identity`, `position`, `offset`, `size`), `extent`, bounds |
| `controller` | the commands; the same object for the lifetime of the mount |
| `itemRef(id)` | the ref callback that measures an item's element |
| `isRendered()` | whether an item is in this render's projection (for `aria-activedescendant`) |

Options besides `count` or `keys` and `estimate`: `orientation` (`vertical` by default), the layout
fields (`paddingStart`, `paddingEnd`, `gap`, `overscan`, `lanes`, `scrollPaddingStart`, …),
`anchor`, `startEdge`, `endEdge`, `focusCustody`, `seed` (a first window for server rendering),
`direction`, `onDiagnostic` and `onRangeChange`.

## The controller

`scrollToPosition(position, {align, behavior})`, `scrollToIdentity`, `scrollToOffset`, `scrollBy`,
`scrollToEdge`, `invalidate`, `reset`, `reportSize`, `focus`, and the queries `getProjection()`,
`positionAt(offset)` and `boundsOf(target)`.

```tsx
<Button onClick={() => controller.scrollToPosition(4999, {align: 'center'})}>
  Go to row 5,000
</Button>
```

## Measuring

Every rendered item gets `ref={itemRef(item.identity)}`. The browser adapter measures its size in
the axis's direction, under the current epoch: a stale element or an older epoch never changes the
current item. An item that grows or shrinks later is measured again without a call.

## Two axes

A grid is two hooks on the same container, one `vertical` and one `horizontal`. They share the
container's scroll host and nothing else; a row and a column request in the same turn become one
physical scroll write.

## Accessibility

The package renders nothing, so the semantics of the list are yours: give the container a name,
keep focus on an element that stays mounted (or use `focusCustody`), and tell assistive technology
how large the collection is when only a window is in the DOM.
