Axios in React Native - Cannot POST a Blob or File

I'm trying to post the raw data of a picture, using Axios, after taking it with react-native-image-picker.

I successfully generated a blob using this piece of code:

const file = await fetch(response.uri);
const theBlob = await file.blob();

If I inspect the metadata of the blob it's all right: MIME type, size, and so on.

However, when I try to POST it using Axios, using:

axios({
    method: "POST",
    url: "https://my-api-endpoint-api.example.org",
    data: theBlob,
});

what I receive on the API side is this strange JSON payload:

{"_data":{"lastModified":0,"name":"rn_image_picker_lib_temp_77cb727c-5056-4cb9-8de1-dc5e13c673ec.jpg","size":1635688,"offset":0,"type":"image/jpeg","blobId":"83367ee6-fa11-4ae1-a1df-bf1fdf1d1f57","__collector":{}}}

The same code is working fine on React, and I have the same behavior trying with a File object instead of a Blob one.

I see in other answers that I could use something else than Axios, like RNFetchBlob.fetch(), but since I'm using shared functions between the React website and the React Native app, I'd really prefer an approach that allows me to use Axios and Blobs.

Is there some way to work around it?


Updated answer

As pointed out by @T.J.Crowder in the comments, there is a cleaner approach that works around the issue of the React Native host environment, without touching anything else on the code.

It's enough to add this on the index.js file, before everything else:

Blob.prototype[Symbol.toStringTag] = 'Blob'
File.prototype[Symbol.toStringTag] = 'File'

I leave my original answer here under since it's a working alternative if one doesn't want to mess up with the prototype.

Original answer

The described behavior happens because the host environment of React Native does not handle the Blob type nicely: it will actually become just an object.

In fact, if you try to render toString.call(new Blob()) in a component, you'll see [object Blob] in a browser, but [object Object] in React Native.

The matter is that the default transformRequest implementation of Axios will use exactly this method (toString.call) to check if you're passing a Blob or some generic object to it. If it sees you're passing a generic object, it applies a JSON.stringify to it, producing the strange JSON you're seeing POSTed.

This happens exactly here: https://github.com/axios/axios/blob/e9965bfafc82d8b42765705061b9ebe2d5532493/lib/defaults.js#L61
Actually, what happens here is that utils.isBlob(data) at line 48 returns false, since it really just applies toString on the value and checks if it is [object Blob], which as described above is not the case.

The fastest workaround I see here, since you're sure you're passing a Blob, is just to override transformRequest with a function that just returns the data as it is, like this:

axios({
    method: "POST",
    url: "https://my-api-endpoint-api.example.org",
    data: theBlob,
    transformRequest: (d) => d,
});

This will just make the request work.