Adding Crop to Image Upload in React
In this post I’ll show how you can add crop UI for images before your user uploads them to a server.
The full code sample can be found in this sandbox:
The solution discussed in this article is based on react-uploady:
Of which I’ve written about before (intro post, preview&progress post).
One of Uploady’s guiding principals is that it should be easy to mix and match packages as needed. In this case, we’ll do just that - take a few packages and combine them to get the outcome we want:
- uploady as our uploader provider
- upload-button to initiate the upload
- upload-preview to load the preview for the crop
Note: uploady can be interchanged with chunked-uploady or tus-uploady when chunked uploads are required (and supported) or when resumable uploads are, respectively.
Cropping UI is provided by react-image-crop. A popular 3rd party for just this use-case.
In the code above, we use different components from Uploady to render an upload button and the preview component. Once an upload begins, the preview will be displayed automatically.
We pass a custom PreviewComponent for the preview
<UploadPreview
PreviewComponent={ItemPreviewWithCrop}
...
We’ll see how its implemented next.
A lot is going on there. Let’s unpack it:
We wrap our component with the withRequestPreSendUpdate HOC.
const ItemPreviewWithCrop = withRequestPreSendUpdate(props => { ...
This provides us with the ability to tap into the upload flow and introduce new information about the uploaded file. It also allows us to change the file content or even cancel the upload altogether.
The HOC gives access to the info about the upload (requestData prop) and a function (updateRequest) that can be called with data that overrides the request. Calling the function with false will cancel the upload.
Note: The upload is pending until updateRequest is called.
The ReactCrop component from react-image-crop expects a URL of the image to show for the crop UI. This is made available to our component by the fact that its rendered by @rpldy/upload-preview. We simply get ‘url’ from the props and pass it along.
const { url, ...} = props;<ReactCrop
src={url}
crop={crop}
onChange={setCrop}
onComplete={setCrop} />
onUploadCrop is called when the user clicks the crop button (see sandbox for buttons code, omitted for brevity).
const onUploadCrop = useCallback(async () => {
if (updateRequest &&
(crop?.height || crop?.width)) {
requestData.items[0].file =
await cropImage(url, requestData.items[0].file, crop);
updateRequest({ items: requestData.items });
}
}, [...]);
This is when we need to crop the image down based on the crop data we got from react-image-crop. We use a helper (adopted from react-image-crop sample code). The output from this helper (a blob) is used to override the file in the requestData object. We call updateRequest with our updated data, resuming the upload with our cropped image.
upload-preview also gives us isFallback and type props.
return isFallback || type !== PREVIEW_TYPES.IMAGE ?
(<img src={url} alt="fallback img" />) :
(<>
{requestData && !finished ?
(<ReactCrop ...
isFallback will be true in case a preview wasn’t loaded for some reason (ex: an error). We check the type is PREVIEW_TYPES.IMAGE since it really only makes sense to try and crop an image (with react-image-crop at least).
The useItemFinalizeListener hook gives us the ability to react to the upload finishing.
useItemFinalizeListener(() =>{
setFinished(true);
}, id);
This makes it easy for us to remove the buttons and crop UI once the upload is done.
Note: Uploady provides multiple hooks for every life-cycle event of the items and batches that are being uploaded.
In this post we saw how to implement crop for images just before they are uploaded. Hopefully, this will make it easy for you to get a working, cropping and uploading UI in no time.
Check out the sandbox for the entire code sample to see it in action.
Find (and ⭐ if you like) React-Uploady on github or npm to give it a try.
Edit — I made a video tutorial on this subject over on youtube. It shows how to do single image crop and multi image crop before uploading — https://youtu.be/a6FHlPhw6Vg