0

I am trying to submit a cropped an image generated in a react app using react-image-crop,and save it to a Django Rest Api using Axios.

The app uses React, Redux and Axios on the frontend and Django Rest Framework on the backend.

The form was submitting fine without the file and saving in django without the code for the file added.

Now that the file is added to the form submission the server returns a 400 error.

I suspect that I am not submitting the blob in the correct format to the django server, but I am unsure on how to proceed.

Update: I have used axios below to convert the blob url to a blob and now I am trying to a file that I can submit to a django rest api. The form submits to the django rest API without the file, but when the file is add into the form submission, I receive a 400 error. I have Updated the code to reflect my latest integrations. I have included the code where I set the headers to multipart/form-data. The error seems to be in the file conversion process in the onSubmit() method below.

Here is my relevant code: Importing the react-image-crop library.

// Cropper
import 'react-image-crop/dist/ReactCrop.css';
import ReactCrop from 'react-image-crop';

Function inside of a react hook:

const AdCreator = ({ addFBFeedAd }) => {
  const [title, setTitle] = useState('');
  const [headline, setHeadline] = useState('');
  const [ad_text, setAdText] = useState('');
  const cropper = useRef();



  // Cropper
  const [upImg, setUpImg] = useState();
  const imgRef = useRef(null);
  const [crop, setCrop] = useState({ unit: '%', width: 30, aspect: 1.91 / 1 });
  const [previewUrl, setPreviewUrl] = useState();

  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 = useCallback(img => {
    imgRef.current = img;
  }, []);

  const makeClientCrop = async crop => {
    if (imgRef.current && crop.width && crop.height) {
      createCropPreview(imgRef.current, crop, 'newFile.jpeg');
    }
  };
  const makePostCrop = async crop => {
    if (imgRef.current && crop.width && crop.height) {
      createCropPreview(imgRef.current, crop, 'newFile.jpeg');
    }
  };

  const createCropPreview = async (image, crop, fileName) => {
    const canvas = document.createElement('canvas');
    const scaleX = image.naturalWidth / image.width;
    const scaleY = image.naturalHeight / image.height;
    canvas.width = crop.width;
    canvas.height = crop.height;
    const ctx = canvas.getContext('2d');

    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'));
          return;
        }
        blob.name = fileName;
        window.URL.revokeObjectURL(previewUrl);
        setPreviewUrl(window.URL.createObjectURL(blob));
      }, 'image/jpeg');
    });
 };

  const onSubmit = (e) => {
    e.preventDefault();
    const config = { responseType: 'blob' };
    let file = axios.get(previewUrl, config).then(response => {
        new File([response.data], title, {type:"image/jpg", lastModified:new Date()});       
    }); 
    let formData = new FormData();
    formData.append('title', title);
    formData.append('headline', headline);
    formData.append('ad_text', ad_text);
    formData.append('file', file);
    addFBFeedAd(formData);


  };
  return (

The Form portion:

<form method="post" id='uploadForm'>                  
          <div className="input-field">
            <label for="id_file">Upload Your Image</label>
            <br/>
            {/* {{form.file}} */}
          </div>
          <div>
            <div>
              <input type="file" accept="image/*" onChange={onSelectFile} />
            </div>
            <ReactCrop
              src={upImg}
              onImageLoaded={onLoad}
              crop={crop}
              onChange={c => setCrop(c)}
              onComplete={makeClientCrop}
              ref={cropper}
            />
            {previewUrl && <img alt="Crop preview" src={previewUrl} />}
          </div>

            <button className="btn darken-2 white-text btn-large teal btn-extend" id='savePhoto' onClick={onSubmit} value="Save Ad">Save Ad</button>

        </form>

Here is the Axios Call:

 export const addFBFeedAd = (fbFeedAd) => (dispatch, getState) => {
  setLoading();
  axios
    .post(`http://localhost:8000/api/fb-feed-ads/`, fbFeedAd, tokenMultiPartConfig(getState))
    .then((res) => {
      dispatch(createMessage({ addFBFeedAd: 'Ad Added' }));
      dispatch({
        type: SAVE_AD,
        payload: res,
      });
    })
    .catch((err) => dispatch(returnErrors(err)));
}

Here Is where I set the headers to multipart form data

export const tokenMultiPartConfig = (getState) => {
 // Get token from state
  const token = getState().auth.token;

  // Headers
  const config = {
    headers: {
      "Content-type": "multipart/form-data",
    },
  };

  // If token, add to headers config
  if (token) {
    config.headers['Authorization'] = `Token ${token}`;
  }

  return config;
};

The Model:

class FB_Feed_Ad(models.Model):
    title = models.CharField(max_length=100, blank=True)
    headline = models.CharField(max_length=25, blank=True)
    ad_text = models.CharField(max_length=125, blank=True)
    file = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True)

The crop preview blob:

blob:http://localhost:3000/27bb58e5-4d90-481d-86ab-7baa717cc023

I console.log-ed the Cropped Image after the axios call.

File:  
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: undefined
AdCreator.js:169 formData: 
FormData {}
__proto__: FormData

As you can see I am trying to submit the blob image file generated by the react-image-cropper, as part of the form data when the form is submitted. I want to save the cropped image to the Django Rest API.
Any suggestions?

2
  • Update: I have used axios below to convert the blob url to a blob and now I am trying to a file that I can submit to a django rest api. The form submits to the django rest API without the file, but when the file is add into the form submission, I receive a 400 error. I have Updated the code to reflect my latest integrations. I have included the code where I set the headers to multipart/form-data. The error seems to be in the file conversion process in the onSubmit() method below. Commented May 31, 2020 at 5:10
  • i recently posted medium.com/swlh/adding-crop-before-upload-in-react-22dfcf3a95b7 it shows how to combine react-image-crop and react-uploady to easily upload cropped images to a server. Commented Jul 14, 2020 at 14:16

1 Answer 1

2

you should send it as "Content-Type": "multipart/form-data" to django imageField. So you should convert your blob file appropriately:

let cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
let arr = this.cropImg.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 imageCrop = new File([u8arr], 'imagename', { type: mime });

const fd = new FormData();
fd.append("avatar", imageCrop);

// send fd to axios post method. 
// You should pass in post request "Content-Type": "multipart/form-data" inside headers.
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for clarifying this. I have updated my code to submit with the proper headers as multipart/form-data. I have also used axios to convert the blob from the preview url to a file. I am using react hooks, and react-image-crop instead of the library you are referencing. I have updated my post with changes made in response to your advice. I am still recieving a 400 error. Could you please tell me how I need to convert the blob file in the onSubmit() function?
I have console logged the file after the file is created in onSubmit() and the formData after all items have been added in on Submit(). I have also added the blob url from the crop preview src. I added all three to the end of my post. When I run your code with mine I get TypeError: file.split is not a function or cropper.getCroppedCanvas() is not a function. Probably due to the libraries being different. Thank you for all of your help.
The solution I provided is applicable for react-cropper (npmjs.com/package/react-cropper). You may use it if it would be also useful in your situation.
Thank you I will look into this library.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.