1

I try to make a patch request to an api in order to upload a file which i can make it work using Postman but not in my react project where i use axios. (response is status 200 but the file i try to upload is not getting uploaded).

So i check the code that postman generates for that request (it is generated for node.js) and what seems different from what i do is that in headers besides

'Content-Type': 'application/json', 
'Authorization': 'Bearer randomstring.randomstring.randomstring',

he also uses the headers from the FormData. So what is the equivalent of formData.getHeaders() in react?

var axios = require('axios');
var FormData = require('form-data');
var fs = require('fs');
var data = new FormData();
data.append('userImageFile', fs.createReadStream('/C:/Users/George/Downloads/dummy.jpg'));

var config = {
  method: 'patch',
  url: 'https://api.services.thenameoftheapi.net/api/v3/billing/cebfecea-ff40-4e1f-9fb0-ca588496c787/',
  headers: { 
    'Content-Type': 'application/json', 
    'Authorization': 'Bearer randomstring.randomstring.randomstring', 
    ...data.getHeaders()
  },
  data : data
};

axios(config)
.then(function (response) {
  console.log(JSON.stringify(response.data));
})
.catch(function (error) {
  console.log(error);
});

PS: Also what headers does .getHeaders() extract from form data if form data only contains the file i uploaded?

var userImageFile;

function onFileFormSubmit(e) {
  e.preventDefault();
  const formData = new FormData();
  formData.append("userimage", userImageFile);
  // var headers = formData.getHeaders() // what would headers contain?
  props.patchUser(formData); //redux saga flow
}

function onFileChange(e) {
  userImageFile= e.target.files[0]
  e.target.nextSibling.click(); // To submit the form
}

// an in return of react component the form is the following
<form onSubmit={onFileFormSubmit}>
   <label htmlFor={"upload" + i} className="table_upload">Upload</label>
   <input className="d-none" id={"upload" + i} onChange={onFileChange} type="file"></input>
   <button className="d-none" type="submit"></button>
</form>

The code that makes the actually patch request is the following

import axios from "axios"

const axiosApi = axios.create({
    baseURL: 'https://api.services.thenameoftheapi.net/api/v3',
    'Authorization': 'Bearer '
})

axiosApi.interceptors.request.use(function (config) {

    let access_token = localStorage.getItem("accessToken");
    config.headers = {
      ...config.headers,
      'Authorization': 'Bearer ' + access_token
    }
    config = {...config, Authorization: 'Bearer ' + access_token}
    return config;
  });


export async function patch(url, data, config = {}) {
    console.log('url = ', url); // This outputs correct url
    console.log('data = ', data); // This outputs FormData {}
    console.log('config = ', config); // Empty now but exists to pass parameters if needed
    var headers = {};
    for (var pair of data.entries()) {
        console.log(pair[0]+ ', ' + pair[1]);     // This outputs userimage, [object File]
    }
  
    if ('headers' in config && config.headers ) {
      headers = config.headers
      delete config.headers;
    }
    return axiosApi
      .patch(url, { ...data }, { params: { ...config }, headers: headers })
      .then( response => response.data)
  }
Professor Chaos
  • 195
  • 1
  • 7
  • The [form-data](https://www.npmjs.com/package/form-data) npm package is only a port of the native browser FormData. The NodeJS version allows you to get the Content-Type of the request along with [the boundary](https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data) using `.getHeaders()`. But in browsers, it's native, and it handles boundaries just fine by itself, so you don't need to set the `Content-Type` header, and in fact, you should not do it ([Scroll to the yellow warning](https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects)) – blex May 27 '21 at 14:59
  • And when sending files, the content-type can never be `application/json`. It will always be `multipart/form-data`. Your first example just works because `...data.getHeaders()` overwrites that property (Try `console.log(config)` in your first example to see what I mean) – blex May 27 '21 at 15:01
  • I did not specify content-type in the request this time and by using the network tab in chrome dev tools i see that in the request headers the content-type is `content-type: application/json;charset=UTF-8` I thought that it will automatically use `multipart/form-data` since i left it empty – Professor Chaos May 27 '21 at 15:15
  • And if i set it by myself to multipart/form-data i can see in the network tab in the request headers `content-type: multipart/form-data` But i get error status 400 when i set it to multipart/form-data – Professor Chaos May 27 '21 at 15:20
  • Can you show the code that makes the request, in your React app? The snippet you showed us is just creating a new FormData, but not doing anything with it – blex May 27 '21 at 15:56
  • Will add it now – Professor Chaos May 27 '21 at 16:10

1 Answers1

1

You are doing weird stuff with the headers. Here is a slightly simplified version which should theoretically work as-is:

import axios from "axios";

const axiosApi = axios.create({
  baseURL: 'https://api.services.thenameoftheapi.net/api/v3'
});

axiosApi.interceptors.request.use(function (config) {
  config.headers = {
    ...config.headers,
    'Authorization': 'Bearer ' + localStorage.getItem("accessToken")
  };
  return config;
});

// I renamed "config" to "params", to avoid confusion
export function patch(url, data, params = {}) {
  let headers = {};

  if (params.headers) {
    headers = params.headers
    delete params.headers;
  }

  // Here, don't do { ...data }, you are destroying the FormData instance!
  return axiosApi
    .patch(url, data, { params, headers })
    .then(({ data }) => data)
}

This will result in the request having this header:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxXxXxXx
blex
  • 22,377
  • 5
  • 35
  • 65