React bindings for @poly67/core:
a drag-and-drop <ImageOptimizer> component and a useImageOptimizer hook
that shrink uploaded images to fit strict size/dimension requirements —
entirely in the browser, no server round-trip.
See the root README for the full guide and the algorithm explanation.
npm install @poly67/reactreact and react-dom (18 or 19) are peer dependencies.
import { ImageOptimizer } from "@poly67/react";
function ProfilePhotoUpload() {
return (
<ImageOptimizer
maxSizeKB={20}
maxWidth={200}
maxHeight={200}
format="jpeg"
quality={0.9}
autoOptimize
preview
onProgress={(p) => console.log(p.stage, p.progress)}
onSuccess={(result) => {
const formData = new FormData();
formData.append("photo", result.file);
fetch("/api/upload", { method: "POST", body: formData });
}}
onError={(error) => console.error(error)}
/>
);
}Ships with a minimal, accessible default UI (dropzone + native file input + preview + status text), which you can fully replace with a render-prop:
<ImageOptimizer maxSizeKB={20} maxWidth={200} maxHeight={200}>
{({ isDragActive, isOptimizing, previewUrl, result, error, openFileDialog }) => (
<div className={isDragActive ? "dropzone--active" : "dropzone"}>
{previewUrl && <img src={previewUrl} alt="" />}
<button type="button" onClick={openFileDialog} disabled={isOptimizing}>
{isOptimizing ? "Optimizing…" : "Choose photo"}
</button>
{result && <p>{result.sizeKB.toFixed(1)}KB, ready to upload</p>}
{error && <p role="alert">Couldn't process that image.</p>}
</div>
)}
</ImageOptimizer>All OptimizeOptions fields (maxSizeKB, maxWidth, maxHeight, format,
quality, minQuality, background, preserveAspectRatio, fit, crop,
correctOrientation) are accepted directly, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
autoOptimize |
boolean |
true |
Optimize as soon as a file is selected/dropped. |
preview |
boolean |
true |
Show an object-URL preview of the selected file. |
multiple |
boolean |
false |
Allow selecting more than one file. |
accept |
string |
"image/*" |
Forwarded to the file input. |
disabled |
boolean |
false |
Disable interaction. |
className / style |
— | — | Applied to the root element. |
children |
ReactNode | (state) => ReactNode |
— | Custom UI / render prop. |
onProgress |
(p: OptimizeProgress) => void |
— | Pipeline progress updates. |
onSuccess |
(r: OptimizeResult) => void |
— | Called once optimization finishes. |
onError |
(e: unknown) => void |
— | Called if optimization throws. |
When autoOptimize is false, trigger optimization imperatively:
const ref = useRef<ImageOptimizerHandle>(null);
<ImageOptimizer ref={ref} autoOptimize={false} maxSizeKB={20} />
<button onClick={() => ref.current?.optimize()}>Optimize</button>ImageOptimizerHandle: { optimize(), reset(), openFileDialog() }.
For fully custom UIs:
import { useImageOptimizer } from "@poly67/react";
function Uploader() {
const { optimize, isOptimizing, progress, result, error } = useImageOptimizer({
maxSizeKB: 20,
maxWidth: 200,
maxHeight: 200,
});
return (
<input
type="file"
accept="image/*"
onChange={(e) => e.target.files?.[0] && optimize(e.target.files[0])}
/>
);
}Returns { optimize, isOptimizing, progress, result, error, reset, abort }.
Calling optimize() again automatically aborts a still-running previous call.
optimizeImage is re-exported for one-off calls that don't need component
state:
import { optimizeImage } from "@poly67/react";
const { file } = await optimizeImage(rawFile, { maxSizeKB: 20 });MIT