Reputation: 327
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
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.
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