Welcome to our comprehensive tutorial on React Image Cropper Firebase Upload! In this in-depth guide, we'll walk you through the process of integrating a powerful image cropper into your React applications and seamlessly uploading cropped images to Firebase storage.
Full Tutorial
To learn more about the application watch the video below.
App.tsx code snippet
import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css";
import { useState } from "react";
import { getDownloadURL, ref, uploadBytesResumable } from "firebase/storage";
import { projectStorage } from "./firebase/config";
function App() {
const [dataUrl, setDataUrl] = useState<string>(""); //cropped image
const [cropper, setCropper] = useState<any>();
const [image, setImage] = useState(""); // default image preview
const changeHandler = (e: any) => {
e.preventDefault();
let files;
if (e.dataTransfer) {
files = e.dataTransfer.files;
} else if (e.target) {
files = e.target.files;
}
const reader = new FileReader();
reader.onload = () => {
setImage(reader.result as any);
};
reader.readAsDataURL(files[0]);
};
const getCrop = () => {
if (cropper !== undefined) {
setDataUrl(cropper.getCroppedCanvas().toDataURL());
}
};
const UploadImage = async () => {
const fileName = _cropped_png;
const arr = dataUrl.split(",");
if (arr.length < 2) {
throw new Error("Invalid data url");
}
const mime = arr[0].match(/:(.*?);/);
if (!mime) {
throw new Error("Invalid data URL format");
}
const mimeType = mime[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
const FinalFile = new File([u8arr], fileName, { type: mimeType });
//upload to firebase storage
await firebaseUpload(FinalFile);
};
const firebaseUpload = async (image: File): Promise<string | undefined> => {
return new Promise<string | undefined>((resolve, reject) => {
const storageRef = ref(projectStorage, "/croppedImages/image.name"); // directory where the image will be saved;
const metaData = {
contentType: image.type,
contentDisposition: "inline",
};
const uploadTask = uploadBytesResumable(storageRef, image, metaData);
uploadTask.on(
"state_changed",
(snapshot) => {
const percent = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
console.debug(percent); //can be used on percent upload indicators
},
(err) => {
console.error(err);
reject(err);
},
async () => {
try {
const url = await getDownloadURL(uploadTask.snapshot.ref);
console.log(url);
if (url) {
resolve(url); // url can be saved into a database
}
} catch (error) {
console.error(error);
reject(error);
}
}
);
});
};
return (
<div style="display: flex; gap: 4px">
<div>
<Input type="file" onChange={changeHandler} />
<Cropper
src={image}
initialAspectRatio={16 / 9}
guides={false}
responsive={true}
onInitialized={(instance) => {
setCropper(instance);
}}
/>
<button type="button" onClick={getCrop}>
Crop Image
</button>
</div>
<div style="display: flex; flex-direction: column; gap: 4px">
<h2>Preview</h2>
<img width={"300px"} src={dataUrl} />
<button type="button" onClick={UploadImage}>
Upload Image
</button>
</div>
</div>
);
}
export default App;