React

First-class React wrapper with useCrop hook for state management.

Installation

npm install @imazen/crop-image-react

Peer dependencies: react >= 18, react-dom >= 18. The web component is auto-registered on import.

Basic Usage

import { CropImage } from '@imazen/crop-image-react';

function App() {
  return (
    <CropImage
      src="/photo.jpg"
      aspectRatio="16/9"
      onChange={(detail) => console.log(detail.selection)}
      onCommit={(detail) => console.log(detail.riapi.querystring)}
    />
  );
}

The useCrop Hook

For controlled state management, useCrop tracks the current selection, committed RIAPI querystring, and provides a reset function.

import { CropImage, useCrop } from '@imazen/crop-image-react';

function CropEditor() {
  const crop = useCrop();

  return (
    <div>
      <CropImage
        src="/photo.jpg"
        aspectRatio="16/9"
        onChange={crop.onChange}
        onCommit={crop.onCommit}
      />

      <p>Querystring: {crop.querystring}</p>
      <button onClick={crop.reset}>Reset</button>
    </div>
  );
}

useCrop Options

OptionTypeDescription
initialSelectionCropSelectionStarting crop state. Defaults to 10% inset on all sides.

useCrop Return Value

PropertyTypeDescription
selectionCropSelectionCurrent crop + pad state
onChange(detail) => voidPass to <CropImage onChange>
onCommit(detail) => voidPass to <CropImage onCommit>
setSelection(sel) => voidManually update the selection
reset() => voidReset to initial selection
querystringstringLast committed RIAPI querystring
paramsRecord<string, string>Last committed RIAPI params object

CropImage Props

The wrapper accepts all web component attributes as camelCase props, plus callback props.

PropTypeDescription
srcstringImage URL (required)
mode'crop' | 'crop-pad'Crop mode
aspectRatiostringAspect ratio constraint
shape'rect' | 'circle'Frame shape (circle forces 1:1)
maxZoomnumberMaximum zoom level
aspectRatiosArray<{w, h, label?}>Array of ratio choices
minWidthnumberMin crop width (source px)
minHeightnumberMin crop height (source px)
maxWidthnumberMax crop width (source px)
maxHeightnumberMax crop height (source px)
valueCropSelectionControlled selection value
namestringForm field name
adapterstringRIAPI adapter name
disabledbooleanDisable interaction
onChange(detail) => voidCalled on every drag move
onCommit(detail) => voidCalled when drag ends

Ref Access

Use a ref to access the underlying DOM element and its selection property:

import { useRef } from 'react';
import { CropImage, type CropImageRef } from '@imazen/crop-image-react';

function App() {
  const ref = useRef<CropImageRef>(null);

  const logSelection = () => {
    console.log(ref.current?.selection);
    console.log(ref.current?.element);
  };

  return (
    <>
      <CropImage ref={ref} src="/photo.jpg" />
      <button onClick={logSelection}>Log Selection</button>
    </>
  );
}

TypeScript

All types are exported from the package:

import type {
  CropImageProps,
  CropImageRef,
  CropChangeDetail,
  CropRect,
  PadRect,
  CropSelection,
  AspectRatio,
  CropConfig,
} from '@imazen/crop-image-react';

Complete Example

import { CropImage, useCrop } from '@imazen/crop-image-react';
import type { CropSelection } from '@imazen/crop-image-react';

function PhotoCropper({ imageUrl }: { imageUrl: string }) {
  const crop = useCrop();

  const handleSave = () => {
    // Send the RIAPI querystring to your image server
    fetch(`/api/resize?src=${imageUrl}${crop.querystring}`)
      .then(res => res.blob())
      .then(blob => {
        // Use the cropped image
      });
  };

  return (
    <div>
      <CropImage
        src={imageUrl}
        aspectRatio="4/3"
        edgeSnap={0.03}
        adapter="imageflow"
        onChange={crop.onChange}
        onCommit={crop.onCommit}
        style={{ maxWidth: '600px' }}
      />

      <div>
        <pre>{crop.querystring}</pre>
        <button onClick={crop.reset}>Reset</button>
        <button onClick={handleSave}>Save</button>
      </div>
    </div>
  );
}

SSR note: The web component registration is guarded with typeof window !== 'undefined', so the import is safe for Next.js and other SSR frameworks. The element renders as an empty custom element during SSR and hydrates on the client.