0

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.

  • This post might be useful for your issue. [React crop preview not rendering](https://stackoverflow.com/questions/72656236/reactcrop-preview-not-rendering/72669427#72669427) – Cyborg Apr 28 '23 at 07:20

0 Answers0