# Hooks

> [llms.txt](https://zriz.dev/llms.txt)

Seven hooks from `@zriz/react` that read the cropper and build parts of your own.

Call a hook in any component inside `Root`. This label reads `edited` once the crop changes:

```tsx
"use client";

import { useCropperState } from "@zriz/react";

export const Status = () => {
  const pristine = useCropperState((s) => s.pristine);

  return <span>{pristine ? "original" : "edited"}</span>;
};
```

Outside `Root`, pass the cropper yourself. It goes last in `useCropperState`, `useCropperValue` and `useCropperFrame`, and in the options of `useZoom`. Without it, they throw the error listed on [errors](https://zriz.dev/docs/errors.md).

## Call methods from your own buttons

Each `Root` runs one cropper. Pass a ref to `actionsRef` to reach it once `Root` has mounted. The ref stays set while a suspense boundary shows its fallback, and is `null` while `<Activity>` hides `Root`. When `Root` unmounts, the ref clears with the passive effects, so a parent's `useLayoutEffect` in that commit still reads the cropper. On a keyed remount, the old `Root` lets go after the new one takes the ref. A ref object or a stable callback with no cleanup keeps the new cropper. An inline callback gets `null` last, and a cleanup your callback returns runs last, so neither suits a keyed `Root`. [Cropper](https://zriz.dev/docs/cropper.md) lists its methods.

```tsx
"use client";

import type { Cropper as CropperApi } from "@zriz/react";
import * as Cropper from "@zriz/react/cropper";
import { useRef } from "react";

export const Editor = ({ src }: { src: string }) => {
  const cropper = useRef<CropperApi>(null);

  const save = () => {
    console.log(cropper.current?.getPixelCrop());
  };

  return (
    <>
      <Cropper.Root src={src} actionsRef={cropper}>
        <Cropper.Viewport
          style={{ maxWidth: 480, height: 360 }}
        >
          <Cropper.Image />
          <Cropper.Mask />
          <Cropper.Area aria-label="Crop area" />
        </Cropper.Viewport>
      </Cropper.Root>
      <button onClick={save}>Save</button>
    </>
  );
};
```

## The hooks

### `useCropper`

`useCropper(options?)` creates the cropper in a component above `Root`. Use it when that component needs the cropper during render. Hand the cropper to `Root` and to any control:

```tsx
"use client";

import { attachImage } from "@zriz/core/dom";
import { useCropper } from "@zriz/react";
import * as Cropper from "@zriz/react/cropper";
import { useEffect } from "react";

export const Editor = ({ src }: { src: string }) => {
  const cropper = useCropper({ maxZoom: 8 });

  useEffect(() => () => attachImage(cropper, null), [cropper]);

  return (
    <>
      <button onClick={() => cropper.reset()}>Reset</button>
      <Cropper.Root cropper={cropper} src={src}>
        <Cropper.Viewport
          style={{ maxWidth: 480, height: 360 }}
        >
          <Cropper.Image />
          <Cropper.Mask />
          <Cropper.Area aria-label="Crop area" />
        </Cropper.Viewport>
      </Cropper.Root>
    </>
  );
};
```

It takes the `createCropper` options on [cropper](https://zriz.dev/docs/cropper.md) except `clock`, plus the controlled `aspect`, `rotation` and `zoom`, and `cropper` to reuse one you already made. `disabled`, `maxZoom`, `minSize`, `value` and the two callbacks follow later renders. The cropper reads `defaultValue`, `image`, `focalLength` and `plugins` once, when it is made.

When `Root` gets `cropper`, it ignores its own `defaultValue`, `image`, `focalLength` and `plugins`, because the cropper already exists. Give each option to `useCropper` or to `Root`, never to both.

`Root` frees the photo only for a cropper it made. Free a `useCropper` cropper's photo yourself when the component unmounts, with `attachImage(cropper, null)` from `@zriz/core/dom`, as the snippet does. Add `@zriz/core` as in [exporting](https://zriz.dev/docs/exporting.md).

`Root` keeps its photo while `<Activity>` hides it. React runs no cleanup when it removes a tree that is hidden, so `Root` cannot free the photo then. To remove a hidden `Root`, make the cropper with `useCropper` above it and call `attachImage(cropper, null)` yourself.

Pass one cropper to one `Root`. `shape`, `motion` and `recenterDelay` belong to the cropper, so a second `Root` overwrites them, even with its defaults. Keep `Instructions` in the `Root` that holds `Area`. Your own parts and hooks can reach the cropper through `CropperContext` or the `cropper` argument.

### `useCropperState`

`useCropperState(selector, cropper?)` returns the part of the snapshot you select and renders again when it changes. The snapshot changes when a state flips, never on every frame.

- `ready` `boolean`

  Is `true` once the cropper knows the photo's size. The `image` option makes it `true` before the file loads, so wait for `onLoad` before you export
- `interacting` `ChangeReason | null`

  Names the gesture in progress
- `settling` `boolean`

  Is `true` while any animation runs: a thrown dial, the recentre, a rotate or flip
- `pristine` `boolean`

  Is `true` while the crop matches the reset value
- `disabled` `boolean`

  Is `true` while the cropper refuses gestures and most writes, see [cropper](https://zriz.dev/docs/cropper.md)
- `aspect` `Aspect`

  Holds the current aspect
- `quarterTurns` `0 | 1 | 2 | 3`

  Holds the current quarter turns
- `mirrored` `boolean`

  Is `true` while the photo is flipped
- `committed` `CropValue`

  Holds the last committed value

Return one field or a primitive. A selector that builds a new object or array on every call makes React render forever.

### `useCropperValue`

`useCropperValue(cropper?)` returns the last committed value and renders once per commit. A new `value` prop and writes with `emit: false` change it without a commit, so the hook does not render for them. The first fit when a photo loads does not commit either, but the hook renders when a photo arrives or leaves, so it shows that fit. The fields are on [value](https://zriz.dev/docs/value.md).

```tsx
"use client";

import { useCropperValue } from "@zriz/react";

export const Angle = () => {
  const value = useCropperValue();

  return <p>{value.rotation.toFixed(1)}°</p>;
};
```

### `useCropperFrame`

`useCropperFrame(listener, cropper?)` calls the listener on every frame that moved, without a render. The frame holds `time`, `value` and `state`, the crop in image pixels, which is `null` before a photo loads. [Cropper](https://zriz.dev/docs/cropper.md) lists its fields. Write the live angle straight into the DOM:

```tsx
"use client";

import { useCropperFrame } from "@zriz/react";
import { useRef } from "react";

export const LiveAngle = () => {
  const label = useRef<HTMLOutputElement>(null);

  useCropperFrame(({ value }) => {
    const angle = value.rotation.toFixed(1);

    if (label.current) label.current.textContent = `${angle}°`;
  });

  return <output ref={label} />;
};
```

### `useZoom`

`useZoom(options?)` connects a slider of your own to the zoom. Each drag counts as one `zoom-control` gesture on [events](https://zriz.dev/docs/events.md). It takes two options, `max` and `cropper`. [Zoom](https://zriz.dev/docs/zoom.md) builds a range input with it. It returns:

- `value` `number`

  Holds the zoom rounded to 3 decimals. It updates every frame
- `min` `number`

  Is always 1, the largest crop
- `max` `number`

  Is the `max` option, or the cropper's `maxZoom`, 15 by default. `max` sets the slider's range only. The cropper still stops at `maxZoom`
- `dragging` `boolean`

  Is `true` while the cropper has a `zoom-control` gesture open, so every `Zoom` and every `useZoom` on one cropper reads the same. A press on `Zoom` holds it until release. A `useZoom` change holds it until the last held pointer or key lifts, or, with nothing held, 150 ms after the last change
- `onValueChange` `(zoom) => void`

  Starts the gesture on the first call and sets the zoom. With no pointer or key held, the gesture ends 150 ms after the last call
- `onValueCommitted` `() => void`

  Ends the gesture. Call it when the slider lets go. The hook also ends it when the last held pointer or key lifts anywhere in the window, when the window loses focus and on unmount

### `useCoverScale`

`useCoverScale({ image, crop, rotation })` returns the smallest scale that keeps a straightened photo free of empty corners, for a crop centred on it. It calls no React hooks, so it is safe in loops and conditions. Outside React, `coverScale(image, crop, radians)` from `@zriz/core`, installed as in [exporting](https://zriz.dev/docs/exporting.md), does the same.

```tsx
"use client";

import { useCoverScale } from "@zriz/react";

export const Scale = () => {
  const scale = useCoverScale({
    image: { width: 4032, height: 3024 },
    crop: { width: 4032, height: 3024 },
    rotation: 5,
  });

  return <p>{scale.toFixed(4)}</p>;
};
```

A full-frame crop of a 4032 × 3024 photo, straightened 5°, needs 1.1124. The photo grows 11.2%.

### `useRender`

`useRender(options)` renders an element the way every zriz part does. Use it to build a part of your own. This is the label from the top of the page as a part:

```tsx
"use client";

import { useCropperState, useRender } from "@zriz/react";
import type { ClassName, RenderProp } from "@zriz/react";

type EditedState = {
  pristine: boolean;
};

type EditedProps = {
  render?: RenderProp<EditedState>;
  className?: ClassName<EditedState>;
};

export const Edited = ({ render, className }: EditedProps) => {
  const pristine = useCropperState((s) => s.pristine);

  return useRender({
    tag: "span",
    state: { pristine },
    render,
    className,
    props: { children: pristine ? "original" : "edited" },
  });
};
```

Your part follows the `render` and `className` rules on [composition](https://zriz.dev/docs/composition.md). It takes:

- `tag` `keyof JSX.IntrinsicElements` required

  Sets the tag, for example `"span"`
- `state` `S` required

  Goes to `render` and `className` functions
- `props` `object` required

  Sets the element's props. Take `render` and your own props out first, as the parts do
- `ref` `Ref<Element>`

  Attaches to the element
- `render` `RenderProp<S>`

  Replaces the element, as on every part
- `className` `ClassName<S>`

  Sets the class: a string, or a function of state
