Danny
Danny

Reputation: 327

React Image-crop showing error as it is not uploading the image in the proper form and is getting an error from the backend

I am get an error while uploading a cropped image using react-image-crop. I think, I am not properly converting the base64 to file type before uploading or that function isn't running?. I am new to react and javascript so lots of things, still confuses me. Can anyone please have a look at the code and help resolve the issue?

I am using django rest api.

This is the link to the package:

https://github.com/DominicTobias/react-image-crop

This is the error I am getting from the backend while uploading.

{profile_pic: ["The submitted data was not a file. Check the encoding type on the form."]}
profile_pic: ["The submitted data was not a file. Check the encoding type on the form."]

This is the code.

function getResizedCanvas(canvas, newWidth, newHeight) {
  const tmpCanvas = document.createElement("canvas");
  tmpCanvas.width = newWidth;
  tmpCanvas.height = newHeight;

  const ctx = tmpCanvas.getContext("2d");
  ctx.drawImage(
    canvas,
    0,
    0,
    canvas.width,
    canvas.height,
    0,
    0,
    newWidth,
    newHeight
  );

  return tmpCanvas;
}


export default function ProfilePicEdit() {

    const [{user}, dispatch] = useStateValue()

    const { register, handleSubmit } = useForm();

    const [upImg, setUpImg] = useState();
    // const [image, setImage] = useState(null);
    const imgRef = useRef(null);
    const previewCanvasRef = useRef(null);
    const [crop, setCrop] = useState({ unit: "%", width: 30, aspect: 1 / 1 });
    const [completedCrop, setCompletedCrop] = useState(null);
    
    const classes = useStyles();

    const onSelectFile = (e) => {
        if (e.target.files && e.target.files.length > 0) {
        const reader = new FileReader();
        reader.addEventListener("load", () => setUpImg(reader.result));
        reader.readAsDataURL(e.target.files[0]);
        // setImage(
        //   {image: e.target.files[0]}
        //   )
        }
    };

    const onLoad = useCallback((img) => {
        imgRef.current = img;
    }, []);

    useEffect(() => {
        if (!completedCrop || !previewCanvasRef.current || !imgRef.current) {
        return;
        }

        const image = imgRef.current;
        const canvas = previewCanvasRef.current;
        const crop = completedCrop;

        const scaleX = image.naturalWidth / image.width;
        const scaleY = image.naturalHeight / image.height;
        const ctx = canvas.getContext("2d");

        canvas.width = crop.width * pixelRatio;
        canvas.height = crop.height * pixelRatio;

        ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
        ctx.imageSmoothingQuality = "high";

        ctx.drawImage(
        image,
        crop.x * scaleX,
        crop.y * scaleY,
        crop.width * scaleX,
        crop.height * scaleY,
        0,
        0,
        crop.width,
        crop.height
        );

    const reader = new FileReader()
    canvas.toBlob(blob => {
        reader.readAsDataURL(blob)
        reader.onloadend = () => {
            dataURLtoFile(reader.result, `sample.jpg`)
        }
    })

    const dataURLtoFile = (dataurl, filename) => {
        let arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), 
        n = bstr.length, 
        u8arr = new Uint8Array(n);
                
        while(n--){
            u8arr[n] = bstr.charCodeAt(n);
        }
        let croppedImage = new File([u8arr], filename, {type:mime});
        setUpImg({upImg: croppedImage }) 
    }

    }, [completedCrop]);


    const onSubmit = () => {
        let formData = new FormData();

        // console.log(upImg)
        formData.append('profile_pic', upImg);

        axiosInstance.put('api/profile/update/', formData)
        // window.location.reload();
    }


    return (
    <div className="imagecropper">
        <form className={classes.form} noValidate onSubmit={handleSubmit(onSubmit)}>
            <Grid item xs={6}>
                <label htmlFor="profile-pic">
                    <input
                    accept="image/*"
                    className={classes.input}
                    id="profile-pic"
                    onChange={onSelectFile}
                    name="image"
                    type="file"
                    ref={register}
                />                  {console.log(upImg)}
                    <div className="profile_pic__edit_main">
                        {upImg === undefined ? 
                            <Avatar src={user && user.profile_pic} alt={user && user.username}
                                className="profile__pic_edit"
                            />
                            : <Avatar src={upImg} className="profile__pic_edit" alt="" />
                        }
                        <div className="profile_pic__edit_icon">
                            <IconButton color="primary" component="span">
                                <PhotoCamera fontSize="large" />
                            </IconButton>
                        </div>
                    </div>
                </label>   
            </Grid>      
                <ReactCrop
                    src={upImg}
                    onImageLoaded={onLoad}
                    crop={crop}
                    onChange={(c) => setCrop(c)}
                    onComplete={(c) => setCompletedCrop(c)}
                />
            {/* <div>
                <canvas
                ref={previewCanvasRef}
                // Rounding is important so the canvas width and height matches/is a multiple for sharpness.
                style={{
                    width: Math.round(completedCrop?.width ?? 0),
                    height: Math.round(completedCrop?.height ?? 0)
                }}
                />
            </div> */}

            <Button
                type="submit"
                fullWidth
                variant="contained"
                color="primary"
                className={classes.submit}
            >
                Update
            </Button>
    </form>
    </div>
  );

}

Thank you

Upvotes: 4

Views: 3392

Answers (1)

armin yahya
armin yahya

Reputation: 1496

there is no need to use useEffect and useCallback in this code. ReactCrop is giving you onComplete so the only thing you need to do is to start drawing after that.

api error:

in the above code you are sending base64 string to the api but as we can see in error api except for File format. also setting name to blob is necessary to recognize as File.

i collect these changes and this code should be working:

export default function ProfilePicEdit() {
  const [upImg, setUpImg] = useState();
  const imgRef = useRef(null);
  const canvasRef = useRef(null);
  const [crop, setCrop] = useState({ unit: "%", width: 30, aspect: 1 / 1 });
  const croppedImage = useRef(null);

  const onSelectFile = (e) => {
    if (e.target.files && e.target.files.length > 0) {
      const reader = new FileReader();
      reader.addEventListener("load", () => setUpImg(reader.result));
      reader.readAsDataURL(e.target.files[0]);
    }
  };

  const onLoad = (img) => {
    imgRef.current = img;
  };

  const onCropComplete = (crop) => {
    makeClientCrop(crop);
  };

  const makeClientCrop = async (crop) => {
    if (imgRef.current && crop.width && crop.height) {
      croppedImage.current = await getCroppedImg(
        imgRef.current,
        crop,
        "newFile.jpeg"
      );
    }
  };

  const getCroppedImg = (image, crop, fileName) => {
    if (!canvasRef.current || !imgRef.current) {
      return;
    }
    const canvas = canvasRef.current;
    const scaleX = image.naturalWidth / image.width;
    const scaleY = image.naturalHeight / image.height;
    const ctx = canvas.getContext("2d");

    canvas.width = crop.width * pixelRatio;
    canvas.height = crop.height * pixelRatio;

    ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
    ctx.imageSmoothingQuality = "high";
    ctx.drawImage(
      image,
      crop.x * scaleX,
      crop.y * scaleY,
      crop.width * scaleX,
      crop.height * scaleY,
      0,
      0,
      crop.width,
      crop.height
    );

    return new Promise((resolve, reject) => {
      canvas.toBlob((blob) => {
        if (!blob) {
          //reject(new Error('Canvas is empty'));
          console.error("Canvas is empty");
          return;
        }
        blob.name = fileName;
        resolve(blob);
      }, "image/jpeg");
    });
  };

  const onSubmit = (e) => {
    let formData = new FormData();
    formData.append("profile_pic", croppedImage.current,
      croppedImage.current.name);

    axiosInstance.put('api/profile/update/', formData)
    window.location.reload();
  };

  return (
    <div className="imagecropper">
        <form className={classes.form} noValidate onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label htmlFor="profile-pic">
            <input
              accept="image/*"
              id="profile-pic"
              onChange={onSelectFile}
              name="image"
              type="file"
            />
            <div className="profile_pic__edit_main">
              <img
                style={{ width: 40, height: 40 }}
                src={upImg}
                className="profile__pic_edit"
                alt=""
              />
            </div>
          </label>
        </div>
        <ReactCrop
          src={upImg}
          onImageLoaded={onLoad}
          crop={crop}
          onChange={(c) => setCrop(c)}
          onComplete={onCropComplete}
        />
        <div>
          <canvas
            ref={canvasRef}
          />
        </div>
            <Button
                type="submit"
                fullWidth
                variant="contained"
                color="primary"
                className={classes.submit}
            >
                Update
            </Button>
    </form>
    </div>
  );

}

Upvotes: 1

Related Questions