# Controlled state

Own the page, zoom, rotation and fullscreen state in your app, or let the viewer keep it, with one consistent pattern.

Every piece of viewer state follows the same trio: `x` controls the value, `defaultX` sets its initial value while uncontrolled, and `onXChange` reports every change in both modes.

| State      | Controlled   | Initial             | Change               |
| ---------- | ------------ | ------------------- | -------------------- |
| Page       | `page`       | `defaultPage`       | `onPageChange`       |
| Zoom       | `scale`      | `defaultScale`      | `onScaleChange`      |
| Rotation   | `rotation`   | `defaultRotation`   | `onRotationChange`   |
| Fullscreen | `fullscreen` | `defaultFullscreen` | `onFullscreenChange` |

## Basics

Pass a value and its change handler to put that state in your app.

**Demo: Page, zoom and rotation controlled from outside**

```tsx
import { PdfViewer, type Rotation } from '@kingtech/react-pdf-viewer';
import { useState } from 'react';

export default function Demo() {
  const [page, setPage] = useState(1);
  const [scale, setScale] = useState(1);
  const [rotation, setRotation] = useState<Rotation>(0);

  return (
    <>
      <p className="demo-controls">
        <button type="button" className="demo-button" onClick={() => setPage(99)}>
          Go to page 99
        </button>
        <button
          type="button"
          className="demo-button"
          onClick={() => setScale((s) => Math.min(5, s + 0.25))}
        >
          Zoom +25%
        </button>
        <button
          type="button"
          className="demo-button"
          onClick={() => {
            setPage(1);
            setScale(1);
            setRotation(0);
          }}
        >
          Reset
        </button>
        <output>
          page {page} · {Math.round(scale * 100)}% · {rotation}°
        </output>
      </p>
      <PdfViewer
        source="/samples/multipage.pdf"
        page={page}
        onPageChange={setPage}
        scale={scale}
        onScaleChange={setScale}
        rotation={rotation}
        onRotationChange={setRotation}
      />
    </>
  );
}
```

The toolbar and keyboard keep working: they call your handlers, and the viewer shows whatever you pass back. Out-of-range values are clamped and reported: “Go to page 99” shows page 40 and calls `onPageChange(40)`.

### Mixing modes

Control only what you need. Leave `x` undefined to let the viewer keep that state, and use `defaultX` to choose where it starts; the demo under [Customization](#customization) opens on page 5 at 125%, rotated.

Switching a prop between controlled and uncontrolled during the component's life logs a development warning, as React inputs do.

## The same pattern in your components

`useControllableState` is the hook the viewer uses. Build your own controls with the same contract.

**Demo: useControllableState in a custom component**

```tsx
import { useControllableState } from '@kingtech/react-pdf-viewer';
import { useState } from 'react';

interface CounterProps {
  value?: number;
  defaultValue?: number;
  onValueChange?: (value: number) => void;
}

/** A component with the same value / defaultValue / onValueChange contract as the viewer. */
function Counter({ value, defaultValue = 0, onValueChange }: CounterProps) {
  const [count, setCount] = useControllableState({
    value,
    defaultValue,
    onChange: onValueChange,
    name: 'value',
  });
  return (
    <button
      type="button"
      className="demo-button"
      onClick={() => setCount((previous) => previous + 1)}
    >
      Clicked {count} times
    </button>
  );
}

export default function Demo() {
  const [controlled, setControlled] = useState(10);

  return (
    <div className="demo-controls">
      <span>Uncontrolled:</span>
      <Counter defaultValue={0} />
      <span>Controlled (capped at 12):</span>
      <Counter value={controlled} onValueChange={(next) => setControlled(Math.min(12, next))} />
    </div>
  );
}
```

The setter is stable and accepts a value or an updater function; `onChange` fires only when the value actually changes.

## Customization

Start an uncontrolled viewer exactly where you want it. The zoom reset button returns to `defaultScale`.

**Demo: Uncontrolled, with chosen starting values**

```tsx
import { PdfViewer } from '@kingtech/react-pdf-viewer';

export default function Demo() {
  // Uncontrolled, but opening where you choose. `defaultScale` is also the reset-zoom target.
  return (
    <PdfViewer
      source="/samples/multipage.pdf"
      defaultPage={5}
      defaultScale={1.25}
      defaultRotation={90}
      zoomReset
    />
  );
}
```

For styling, see the [customization guide](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/customization/).

## Advanced / escape hatch

For one-off commands (“go to page 3”) without owning the state, use the [imperative API](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/imperative-api/).

## Limitations

- **Values cannot be functions.** `useControllableState` treats a function passed to the setter as an updater. Workaround: wrap function values in an object.
- **New documents reset uncontrolled state only.** A new `source` opens on `defaultPage`; a controlled `page` is yours to reset. Workaround: reset your state when you change the source.

## API

- [useControllableState](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/use-controllable-state.md)
- [UseControllableStateOptions](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/use-controllable-state-options.md)
- [SetControllableState](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/set-controllable-state.md)

---

## useControllableState reference

The value / defaultValue / onValueChange pattern as a hook: the one behind every controlled prop of the viewer.

Internal state is used while `value` is `undefined`; `onChange` fires whenever the value changes. The setter is stable. Values must not be functions (a function argument is treated as an updater).

### Used by

- [Controlled state](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/controlled-state.md)

### Import

```ts
import { useControllableState } from '@kingtech/react-pdf-viewer';
```

Signature: `useControllableState(options: UseControllableStateOptions<T>): readonly [T, SetControllableState<T>]` → `readonly [T, SetControllableState<T>]`

### Options

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `value` | `T` | — | Yes | The controlled value; `undefined` = uncontrolled. |
| `defaultValue` | `T \| () => T` | — | Yes | Initial value while uncontrolled. |
| `onChange` | `(value: T) => void` | — | No | Called with every change, controlled or not. |
| `name` | `string` | — | No | Prop name used in development warnings. |

### Source

[packages/react-pdf-viewer/src/hooks/useControllableState.ts](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/hooks/useControllableState.ts#L25)


---

## UseControllableStateOptions reference

Options for `useControllableState`: the controlled value, the initial value while uncontrolled, and the change callback.

### Used by

- [Controlled state](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/controlled-state.md)

### Import

```ts
import type { UseControllableStateOptions } from '@kingtech/react-pdf-viewer';
```

### Properties

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `value` | `T` | — | Yes | The controlled value; `undefined` = uncontrolled. |
| `defaultValue` | `T \| () => T` | — | Yes | Initial value while uncontrolled. |
| `onChange` | `(value: T) => void` | — | No | Called with every change, controlled or not. |
| `name` | `string` | — | No | Prop name used in development warnings. |

### Source

[packages/react-pdf-viewer/src/hooks/useControllableState.ts](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/hooks/useControllableState.ts#L6)


---

## SetControllableState reference

Setter returned by `useControllableState`; accepts a value or an updater function.

### Used by

- [Controlled state](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/controlled-state.md)

### Import

```ts
import type { SetControllableState } from '@kingtech/react-pdf-viewer';
```

Signature: `type SetControllableState<T> = (next: T | (previous: T) => T) => void`

### Source

[packages/react-pdf-viewer/src/hooks/useControllableState.ts](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/hooks/useControllableState.ts#L18)

