I have changed the react-image-crop dependency version and migrated version 9 to 10. The image crop worked fine in the 9th version but in version 10, it returns a null value for the preview canvas. Also,right after putting the crop, in shows a type error in drawImage function. Because the preview canvas ref in canvas component returns a null value in the console.
Here is my controller file
import React, { useCallback, useRef, useState } from "react";
import { useDropzone } from "react-dropzone";
import { ImagesDropZoneView } from "../view/ImagesDropZoneView";
import PropTypes from "prop-types";
import image_util from "util/image_util";
import { DB_IMAGE_SRC } from "constant/database/ImageSource";
import { editPostStyle } from "view/EditPost/style/EditPostStyle";
const ImagesDropzoneController = (props) => {
const {
files,
setFiles,
noOfImages = 0,
isSingleUpload = false,
isRectangle = false,
loadingImageUpdate = false,
allowCustomCrop = false,
} = props;
const [openCropDialog, setOpenCropDialog] = useState(false);
const [sourceImage, setSourceImage] = useState({});
const [openAlert, setOpenAlert] = useState(false);
// Image Crop States
const [imageLoading, setImageLoading] = useState(true);
const [upImg, setUpImg] = useState();
const imgRef = useRef(null);
const previewCanvasRef = useRef(null);
const [completedCrop, setCompletedCrop] = useState(null);
const [crop, setCrop] = useState({
unit: "%",
width: 50,
height: 50,
aspect: isRectangle ? 16 / 9 : 1,
});
const [cropSquareView, setCropSquareView] = useState(!isRectangle);
const [rotateDegree, setRotateDegree] = useState(0);
const handleDrop = useCallback((acceptedFiles) => {
if (acceptedFiles.length !== 0) {
setSourceImage(acceptedFiles[0]);
setOpenCropDialog(true);
}
}, []);
const {
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject,
} = useDropzone({
accept: "image/jpeg, image/png",
onDrop: handleDrop,
multiple: false,
maxSize: 15000000,
});
const onLoad = useCallback(
(img) => {
imgRef.current = img;
setImageLoading(false);
},
[setImageLoading]
);
const handleRemoveImage = (file, index) => {
URL.revokeObjectURL(file.preview);
const images = [...files];
images.splice(index, 1);
setFiles(images);
};
const handleRemoveAll = () => {
// Used to revoke the data uris to avoid memory leaks
files.forEach((file) => URL.revokeObjectURL(file.preview));
setFiles([]);
};
const handleCloseCropDialog = async () => {
handleAlertClose();
setImageLoading(true);
const generatedFile = await image_util.generateCroppedImage(
previewCanvasRef.current,
completedCrop,
sourceImage.name,
isRectangle,
imgRef
);
setFiles((files) =>
[...files].concat(
Object.assign(generatedFile, {
preview: URL.createObjectURL(generatedFile),
source: DB_IMAGE_SRC.NEW,
})
)
);
setOpenCropDialog(false);
setImageLoading(false);
};
const handleAlertOpen = () => {
setOpenAlert(true);
};
const handleAlertClose = () => {
setOpenAlert(false);
setRotateDegree(0);
};
const closeCropDialog = () => {
setOpenCropDialog(false);
setRotateDegree(0);
};
const handleCustomCrop = (e) => {
setCrop((crop) => ({
...crop,
aspect: e.target.checked ? 1 : 16 / 9,
}));
setCropSquareView(e.target.checked);
};
const handleRotateImage = () => {
if (rotateDegree === 270) {
setRotateDegree(0);
} else {
setRotateDegree(rotateDegree + 90);
}
};
return (
<ImagesDropZoneView
files={files}
getRootProps={getRootProps}
getInputProps={getInputProps}
isDragActive={isDragActive}
handleRemoveAll={handleRemoveAll}
handleRemoveImage={handleRemoveImage}
openCropDialog={openCropDialog}
handleCloseCropDialog={handleCloseCropDialog}
sourceImage={sourceImage}
imageLoading={imageLoading}
upImg={upImg}
setUpImg={setUpImg}
imgRef={imgRef}
previewCanvasRef={previewCanvasRef}
completedCrop={completedCrop}
setCompletedCrop={setCompletedCrop}
crop={crop}
setCrop={setCrop}
onLoad={onLoad}
isDragAccept={isDragAccept}
isDragReject={isDragReject}
isSingleUpload={isSingleUpload}
noOfImages={noOfImages}
openAlert={openAlert}
handleAlertOpen={handleAlertOpen}
handleAlertClose={handleAlertClose}
loadingImageUpdate={loadingImageUpdate}
closeCropDialog={closeCropDialog}
allowCustomCrop={allowCustomCrop}
cropSquareView={cropSquareView}
handleCustomCrop={handleCustomCrop}
rotateDegree={rotateDegree}
handleRotateImage={handleRotateImage}
className={editPostStyle}
/>
);
};
ImagesDropzoneController.propTypes = {
files: PropTypes.array.isRequired,
setFiles: PropTypes.func.isRequired,
isSingleUpload: PropTypes.bool,
isRectangle: PropTypes.bool,
loadingImageUpdate: PropTypes.bool,
};
export default ImagesDropzoneController;
View file
import React, { useEffect } from "react";
import ReactCrop from "react-image-crop";
import {
Grid,
Container,
Button,
CardContent,
Typography,
useTheme,
Card,
Switch,
} from "@mui/material";
import { makeStyles } from "@mui/styles";
import image_util from "util/image_util";
import Rotate90DegreesCwIcon from "@mui/icons-material/Rotate90DegreesCw";
const imageCropViewStyle = makeStyles(() => ({
reactCropImageGrid: {
textAlign: "center",
},
cardContent: {},
}));
export default function ImageCropView(props) {
const {
sourceImage,
upImg,
setUpImg,
imgRef,
previewCanvasRef,
completedCrop,
setCompletedCrop,
crop,
setCrop,
onLoad,
allowCustomCrop,
cropSquareView,
handleCustomCrop,
rotateDegree,
handleRotateImage,
handleCloseCropDialog,
} = props;
const classes = imageCropViewStyle();
const theme = useTheme();
useEffect(() => {
const reader = new FileReader();
reader.addEventListener("load", () => setUpImg(reader.result));
reader.readAsDataURL(sourceImage);
}, [sourceImage, setUpImg]);
useEffect(() => {
if (!completedCrop || !previewCanvasRef.current || !imgRef.current) {
return;
}
const image = imgRef.current;
const canvas = previewCanvasRef.current;
const crop = completedCrop;
let scaleX = image.naturalWidth / image.width;
let scaleY = image.naturalHeight / image.height;
const ctx = canvas.getContext("2d");
if (rotateDegree === 90 || rotateDegree === 270) {
canvas.width = crop.height * image_util.pixelRatio;
canvas.height = crop.width * image_util.pixelRatio;
} else {
canvas.width = crop.width * image_util.pixelRatio;
canvas.height = crop.height * image_util.pixelRatio;
}
ctx.save(); //saves the state of canvas
ctx.clearRect(0, 0, canvas.width, canvas.height); //clear the canvas
ctx.translate(canvas.width / 2, canvas.height / 2); //let's translate
ctx.rotate((Math.PI / 180) * rotateDegree); //increment the angle and rotate the image
ctx.translate(-(canvas.width / 2), -(canvas.height / 2)); //let's translate
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width * 4,
crop.height * 4
); //draw the image ;)
ctx.restore(); //restore the state of canvas
canvas.toBlob((blob) => {
setCompletedCrop(blob);
});
}, [completedCrop, imgRef, previewCanvasRef, rotateDegree, setCompletedCrop]);
return (
<Container maxWidth="md">
<Grid
container
direction="row"
justifyContent="center"
alignItems="flex-start"
spacing={1}
>
<Grid item xs={12} sm={7} className={classes.reactCropImageGrid}>
<Card elevation={0} sx={{ height: "auto" }}>
<CardContent>
{!!upImg && (
<ReactCrop
crop={crop}
onChange={(c) => setCrop(c)}
onComplete={(c) => setCompletedCrop(c)}
ruleOfThirds={true}
keepSelection={true}
aspect={cropSquareView ? 1 : 16 / 9}
>
<img
ref={imgRef}
alt="Crop"
src={upImg}
onLoad={onLoad}
style={{
width: theme.breakpoints.values.sm,
backgroundColor: "black",
}}
/>
</ReactCrop>
)}
{allowCustomCrop && (
<div>
Square Crop: Square Crop:{" "}
<Switch
checked={cropSquareView}
onChange={(e) => handleCustomCrop(e)}
/>
<Typography variant="caption">
(drag through diagonal to get the change into effect)
</Typography>
</div>
)}
</CardContent>
</Card>
</Grid>
<Grid item xs={12} sm={5} className={classes.reactCropImageGrid}>
<Card elevation={0}>
<CardContent className={classes.cardContent}>
{!!completedCrop && !!previewCanvasRef && (
<>
<Typography component={"p"} variant={"caption"}>
CROP TO PREVIEW
</Typography>
{console.log(previewCanvasRef)}
</>
)}
{/* {!completedCrop?.width || !completedCrop?.height ? (
<Typography component={"p"} variant={"caption"}>
CROP TO PREVIEW
</Typography>
) : (
<>
{console.log(previewCanvasRef)}
<canvas
ref={imgRef}
// Rounding is important so the canvas width and height matches/is a multiple for sharpness.
// !completedCrop?.width || !completedCrop?.height
style={{
// width: Math.round(completedCrop?.width ?? 0),
// height: Math.round(completedCrop?.height ?? 0),
borderStyle: "solid",
borderWidth: "medium",
width: theme.breakpoints.values.sm / 3,
backgroundColor: "black",
}}
/>
<div>
<Button
onClick={handleRotateImage}
endIcon={<Rotate90DegreesCwIcon />}
>
Rotate
</Button>
</div>
</>
)} */}
</CardContent>
</Card>
</Grid>
<Button
variant="contained"
color="secondary"
onClick={handleCloseCropDialog}
sx={{
borderRadius: 100,
px: "90px",
py: "8px",
mb: "2vh",
bottom: 1,
}}
>
Upload
</Button>
</Grid>
</Container>
);
}
The props in the controller pass through a different view jsx file, I didn't put it out because it doesn't matter. Also, the canvas renders inside a mui dialog box.