uppy/packages/@uppy/react
Merlijn Vos 850c1cb1c5
Add useImageEditor (#6122)
- Create image editor abstraction in `@uppy/components`
- Refactor `Editor` (Preact) and `ImageEditor` (Uppy plugin) to put all
methods in the Uppy plugin class so they can be reused by the headless
abstraction
- Create framework hooks for React, Vue, Svelte
- Update all examples to showcase the new hook
- Symlink the `cropper.scss` (just regular CSS) from
`@uppy/image-editor` into `@uppy/components`, update `build:css` in all
framework packages to copy that file too into their own `dist/` output.
- cropper-js needs basic CSS to do the rotations and stuff which would
be very tedious to write yourself so we ship that CSS file to consumers
too so they can just import it.
- Fix memory leak where we created the image on each render inside the
original Preact plugin UI

```ts
import type { UppyFile } from '@uppy/core'
import { useImageEditor } from '@uppy/react'

interface ImageEditorProps {
  file: UppyFile<any, any>
  close: () => void
}

export function ImageEditor({ file, close }: ImageEditorProps) {
  const {
    state,
    getImageProps,
    getSaveButtonProps,
    getCancelButtonProps,
    getRotateButtonProps,
    getFlipHorizontalButtonProps,
    getZoomButtonProps,
    getCropSquareButtonProps,
    getCropLandscapeButtonProps,
    getCropPortraitButtonProps,
    getResetButtonProps,
    getRotationSliderProps,
  } = useImageEditor({ file })

  return (
    <div className="p-4 max-w-2xl w-full">
      <div className="flex justify-between items-center mb-4">
        <h2 className="text-xl font-bold">Edit Image</h2>
        <button
          type="button"
          onClick={close}
          className="text-gray-500 hover:text-gray-700"
        >
          ✕
        </button>
      </div>

      <div className="mb-4">
        {/* biome-ignore lint/a11y/useAltText: alt is provided by getImageProps() */}
        <img
          className="w-full max-h-[400px] rounded-lg border-2"
          {...getImageProps()}
        />
      </div>

      <div className="mb-4">
        <label className="block text-sm font-medium mb-2">
          Fine Rotation: {state.angle}°
          <input className="w-full mt-1" {...getRotationSliderProps()} />
        </label>
      </div>

      <div className="flex gap-2 flex-wrap mb-4">
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getRotateButtonProps(-90)}
        >
          ↶ -90°
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getRotateButtonProps(90)}
        >
          ↷ +90°
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getFlipHorizontalButtonProps()}
        >
          ⇆ Flip
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getZoomButtonProps(0.1)}
        >
          + Zoom
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getZoomButtonProps(-0.1)}
        >
          - Zoom
        </button>
      </div>

      <div className="flex gap-2 flex-wrap mb-4">
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getCropSquareButtonProps()}
        >
          1:1
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getCropLandscapeButtonProps()}
        >
          16:9
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getCropPortraitButtonProps()}
        >
          9:16
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getResetButtonProps()}
        >
          Reset
        </button>
      </div>

      <div className="flex gap-4 justify-end">
        <button
          className="bg-gray-500 text-white px-4 py-2 rounded-md"
          {...getCancelButtonProps({ onClick: close })}
        >
          Cancel
        </button>
        <button
          className="bg-green-500 text-white px-4 py-2 rounded-md disabled:opacity-50 disabled:bg-green-300"
          {...getSaveButtonProps({ onClick: close })}
        >
          Save
        </button>
      </div>
    </div>
  )
}

export default ImageEditor

```


https://github.com/user-attachments/assets/4b0a99cc-8489-41a2-aa3b-ce88110025bf

---------

Co-authored-by: Prakash <qxprakash@gmail.com>
2026-01-29 11:13:12 +01:00
..
src Add useImageEditor (#6122) 2026-01-29 11:13:12 +01:00
.npmignore @uppy/react: refactor to TS (#5012) 2024-03-27 11:03:34 +01:00
CHANGELOG.md [ci] release (#6046) 2025-10-30 10:10:59 +01:00
LICENSE Move React components to @uppy/react package. 2018-06-18 10:14:08 +02:00
package.json Add useImageEditor (#6122) 2026-01-29 11:13:12 +01:00
README.md Fix links (#5492) 2024-10-29 13:54:00 +01:00
tsconfig.build.json add back framework wrappers for @uppy/status-bar (#5948) 2025-09-17 10:40:57 +02:00
tsconfig.json add back framework wrappers for @uppy/status-bar (#5948) 2025-09-17 10:40:57 +02:00
turbo.json Add useImageEditor (#6122) 2026-01-29 11:13:12 +01:00

@uppy/react

Uppy logo: a smiling puppy above a pink upwards arrow

npm version CI status for Uppy tests CI status for Companion tests CI status for browser tests

React component wrappers around Uppys officially maintained UI plugins.

Uppy is being developed by the folks at Transloadit, a versatile file encoding service.

Example

/** @jsx React */
import React from 'react'
import Uppy from '@uppy/core'
import { DashboardModal } from '@uppy/react'

const uppy = new Uppy()

class Example extends React.Component {
  state = { open: false }

  render() {
    const { open } = this.state
    return (
      <DashboardModal
        uppy={uppy}
        open={open}
        onRequestClose={this.handleClose}
      />
    )
  }
  // ..snip..
}

Installation

$ npm install @uppy/react

Alternatively, you can also use this plugin in a pre-built bundle from Transloadits CDN: Smart CDN. In that case Uppy will attach itself to the global window.Uppy object. See the main Uppy documentation for instructions.

Documentation

Documentation for this plugin can be found on the Uppy website.

License

The MIT License.