React Native fetch throws error when posting multipart/form-data - react-native

This is how I tried to POST an image as multipart/form-data to server.
var photo = { uri: this.state.avatarSource,
type: 'multipart/form-data',
name: 'photo.jpg', };
let formdata = new FormData();
formdata.append('attachment',photo);
fetch(url,
{ method: "POST",
headers: { 'Content-Type': 'multipart/form-data' },
body: formdata
}).then((response) => response.json())
.catch((error) => { alert("ERROR " + error) })
.then((responseData) => { alert("Succes "+ responseData) }).done();
But it shows an error : Expected dynamic type string but had type
object

After 2 days, trying so many things I made the code working with some modifications.
RNFetchBlob.fetch('POST', url, {
'Content-Type': 'multipart/form-data',
}, [
{ name: 'file', filename: 'photo.jpg', type: 'image/png', data: RNFetchBlob.wrap(src) }
]) .then((resp) => {
console.log(resp.text());
}).catch((err) => {
console.log(err);
});

As mentioned in the docs formData.append(name, value, filename);
The value field in it accepts USVString or Blob, since you're passing an object to it therefore it throws an error.
You need to convert your image to blob, append and upload it.
If you've got the base64 of the image, then you can convert it to a blob directly using the fetch api
fetch(base64URL)
.then(res => res.blob())
.then(blob => console.log(blob))
Otherwise you may checkout RN-fetch-blob, their multipart/ formData example.

I slightly modified the solution given by #unknown123. And it worked for me.
Here is the solution.
First, install npm package rn-fetch-blob
import RNFetchBlob from 'rn-fetch-blob';
RNFetchBlob.fetch('PUT', url, {'Content-Type': 'multipart/form-data'},
Platform.OS === 'ios' ? RNFetchBlob.wrap(filePath) :
`RNFetchBlob-${filePath}`)
Please note, in my case, I had different file paths for IOS and Android devices. We handle it in different ways using rn-fetch-blob
Sample filePath variable data for
IOS device -
"/Users/Niveditha/Library/Developer/CoreSimulator/Devices/B41EB910-F22B-4236-8286-E6BA3EA75C70/data/Containers/Data/Application/B88777C6-6B10-4095-AB67-BB11E045C1DE/tmp/react-native-image-crop-picker/img.jpg"
Android device -
"file:///storage/emulated/0/Android/data/com.uploadcameraroll/files/Pictures/img.jpg"

Related

multipart/form-data request failing in react-native

I get the following error when I set the 'Content-Type' as 'multipart/form-data' in react-native.
Below is my code -
const formData = new FormData();
formData.append('org_id', org_id);
formData.append('ans', userAns);
formData.append('remark', userRemark);
formData.append('img', userImg);
files.forEach(file => {
formData.append('files', {
name: file.fileName,
type: file.type,
uri: file.uri,
});
});
const resp = await multiPartInstance({
method: 'PUT',
url: `${apiBaseUrl}/installation/${Iid}/answer/${qid}`,
data: formData,
});
return Promise.resolve(true);
I am using axios for calling apis. multiPartInstance is an axios instance -
const multiPartAccessToken = async (config: AxiosRequestConfig) => {
config.headers = {
Accept: 'application/json',
access_token: useTokenStore.getState().accessToken,
'Content-Type': 'multipart/form-data;',
};
config.timeout = 30000;
return config;
};
I've tried the above with fetch also but I keep getting the same error. The strangest part is that this request hits the server, server sends a response too but I get this error react-native side. I've noticed if I don't use FormData I don't get any error. But I need to use FormData as I have to upload image files.
Environment Details -
Windows version 21H2 (OS Build 22000.376)
react-native 0.66.3
react 17.0.2
axios ^0.24.0
react-native-image-picker ^4.3.0 (used for selecting images)
Flipper version 0.99.0
I've tried the solutions posted on below forums but they didn't work for me.
request formData to API, gets “Network Error” in axios while uploading image
https://github.com/facebook/react-native/issues/24039
https://github.com/facebook/react-native/issues/28551
I am as follow and works perfectly:
const oFormData = new FormData();
images.map((val, index) => {
oFormData.append("image", {
uri: val.uri,
type: val.type,
name: val.fileName
});
});
return axios.post(postServiceUrl, oFormData);
Somehow react-native-blob-util doesn't give this error. I modified my code as below -
import ReactNativeBlobUtil from 'react-native-blob-util';
const fileData = files.map(file => {
return {
name: 'files',
data: String(file.base64),
filename: file.fileName,
};
});
try {
const resp = await ReactNativeBlobUtil.fetch(
'PUT',
`${apiBaseUrl}/installation/${Iid}/answer/${qid}`,
{
access_token: useTokenStore.getState().accessToken,
'Content-Type': 'multipart/form-data',
},
[
...fileData,
// elements without property `filename` will be sent as plain text
{name: 'org_id', data: String(org_id)},
{name: 'ans', data: String(userAns)},
{name: 'remark', data: String(userRemark)},
{name: 'img', data: String(userImg)},
],
);

Android: Network Request Failed when trying to upload image with fetch

I'm trying to upload an image from storage to a restful API but I keep getting Network Request Failed on Android (which means the request doesn't even go through), haven't checked on iOS because I don't need that part yet. API is already working and has been tested with Postman.
The React Native code is:
body.append('vehicles',{
resource_id: 2,
resource: 'vehicles',
cat_file_id: fileId,
active: 1,
vehicles: photo, //<- photo value below
name: 'vehicles',
type: 'image/jpeg'
})
fetch(`${BASE_URL}/files`, {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
Accept: "*/*",
Authorization: 'Bearer '+auth
},
body: body
}).then(response => response.json())
.then(response => {
console.log('IMAGE RESPONSE', response)
})
.catch(error => console.log('ERROR', error))
The photo value looks like file:///storage/emulated/0/DCIM/...
The response:
ERROR TypeError: Network request failed
at XMLHttpRequest.xhr.onerror (fetch.umd.js:473)
at XMLHttpRequest.dispatchEvent (event-target-shim.js:818)
at XMLHttpRequest.setReadyState (XMLHttpRequest.js:574)
at XMLHttpRequest.__didCompleteResponse (XMLHttpRequest.js:388)
at XMLHttpRequest.js:501
at RCTDeviceEventEmitter.emit (EventEmitter.js:189)
at MessageQueue.__callFunction (MessageQueue.js:436)
at MessageQueue.js:111
at MessageQueue.__guard (MessageQueue.js:384)
at MessageQueue.callFunctionReturnFlushedQueue (MessageQueue.js:110)
On Postman the request looks something like this:
Already tried:
Removing Accept header
Changing Accept value to 'application/json'
Removing file:// from the image url
Added android:usesCleartextTraffic="true" to the manifest
Already checked:
No values are null or undefined
There is a working internet connection, all other network requests on the app are working fine
The Auth is correct
React Native version is 0.61.5
I found one missing line in your code let formData = new FormData();. but not sure is that the exact issue here.
By the way here is a sample working code from one of my project, and I customized it with your context.
Add your authentication
replace ImageURI with image path and URL_SAVE_IMAGE endpoint url
const newImage = {
resource_id: 2,
resource: 'vehicles',
cat_file_id: 1,
active: 1,
vehicles: ImageURI,
name: "my_photo.jpg",
type: "image/jpg",
};
let formData = new FormData();
formData.append("vehicles", newImage);
return fetch(URL_SAVE_IMAGE, {
method: 'POST',
headers: {
"Content-Type": "multipart/form-data"
},
body: formData
}).then(response => response.json());
it should work!
What is your fetch(${BASE_URL}/files` backend server .. This usually happens when trying to connect to the backend api on the localhost machine.. Even if you use the IP address of the localhost, it still persists, so it is better to use online server for testing or just use ngrok(https://ngrok.com/) to serve your backend localhost via internet.
in gradle.properties change the flipper version to 0.47.0
try with Xhr, it's working as expected!
const URL = "ANY_SERVER/upload/image"
const xhr = new XMLHttpRequest();
xhr.open('POST', url); // the address really doesnt matter the error occures before the network request is even made.
const data = new FormData();
data.append('image', { uri: image.path, name: 'image.jpg', type: 'image/jpeg' });
xhr.send(data);
xhr.onreadystatechange = e => {
if (xhr.readyState !== 4) {
return;
}
if (xhr.status === 200) {
console.log('success', xhr.responseText);
} else {
console.log('error', xhr.responseText);
}
};
Nothing worked for me except using the Expo FileSystem uploadAsync
uploadImage = async ({ imageUri } }) => FileSystem.uploadAsync(
apiUrl,
imageUri,
{
headers: {
// Auth etc
},
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
fieldName: 'files',
mimeType: 'image/png',
});
Note - imageUri in format of file:///mypath/to/image.png
Happy days!

React-Native, Google Photos API Image Upload

react-native + expo
TL;DR: Does anyone have a working example of react-native/expo working with an in app camera and uploading an image to the 'new' google api?
Authenticate user (working)
Make Album (working)
Make Album Shareable (working)
Take Photo (working)
Get UploadToken (Seems like it is working)
Upload Photo (not working)
Why I think (5) seems like it is working, because the getUploadToken function returns successfully, response 200, and provides a key.
Why I think it might be a dumb service on the other end of (5), I can post just about anything to it and it will return successfully.
My hunch is that there is something wrong with the way I am uploading the image to the /uploads endpoint.
IE: not in the correct format.
this.state.image == {'base64':'base64string','uri':'file://...',...}
I do see an album is being created within my google photos, and I can see that it is set to shareable, with no privileges (users can not comment, or add their own photos)
2 MAKE ALBUM
makeAlbum = () => {
//MAKE ALBUM
fetch('https://photoslibrary.googleapis.com/v1/albums', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer '+this.state.accessToken
},
body: JSON.stringify({
"album": {"title": this.state.albumTemp}
}),
}).then((response) => response.json())
.then((response) => {
[
this.shareAlbum(response),
console.log('make: ',response)
]
});
}
}
3 MAKE ALBUM SHAREABLE
shareAlbum = (response) =>{
this.setState({albumId:response.id})
//MAKE ALBUM SHAREABLE
fetch('https://photoslibrary.googleapis.com/v1/albums/'+this.state.albumId+':share',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer '+this.state.accessToken
}
}).then((response) => response.json())
.then((response)=>{
[
this.setState({shareable:response.shareInfo.shareableUrl}),
console.log('share1: ',this.state.shareable),
console.log('share2: ',response),
]
});
}
4 CAPTURE PHOTO
capturePhoto = async () => {
let image = await this._camera.takePictureAsync({
base64: true,
quality: .5,
width: 1920,
fixOrientation: true
})
this.setState({ image: image, capturing: false })
// delay the capture a few seconds to make sure it's rendered
setTimeout(async () => {
let result = await takeSnapshotAsync(this._previewRef, {
format: 'png',
result: 'tmpfile',
quality: 1
});
//upload the photo to google photos album
this.getUploadToken(image)
}, 1000)
}
5 GET UPLOAD TOKEN
getUploadToken = (image) => {
let name = this.state.image.uri.split('/')
name=name[name.length-1]
fetch('https://photoslibrary.googleapis.com/v1/uploads', {
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',
'Authorization': 'Bearer '+this.state.accessToken,
'X-Goog-Upload-File-Name': name,
'X-Goog-Upload-Protocol': 'raw'
},
body:image,
})
.then((response) => {
console.log('setup upload: ',response)
this.uploadPhoto(response._bodyText);
})
}
6 UPLOAD PHOTO
uploadPhoto = (token) => {
fetch('https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer '+this.state.accessToken,
},
body:JSON.stringify({
"albumId": this.state.albumId,
"newMediaItems": [
{
"description": "Event Photo",
"simpleMediaItem": {
"uploadToken": token
}
}
]
})
})
.then((response) => {
console.log('actual upload: ',response)
this.setState({ ready: true, image: null })
})
}
5 GET UPLOAD TOKEN API works find, just the description is wrong on Google Documentation. Instead of Base64 the input is in form of Binary. I have tried in Postman(below screenshot):
Get Upload Token API:
Get Upload Token API
Upload Media:
enter image description here
I've been trying the same thing in a Node Express app.
Don't forget that you need to upload the image in binary, and not in base64.
It also looks like you don't pass a parameter in step 4 to the function getUploadToken().
My app also returns the "message": "NOT_IMAGE: There was an error while trying to create this media item.".
In my node app i convert the Buffer to a binary with
const fileBinary = file.data.toString('binary')
Step 5 certainly does not work... You are not sending an image at all!
In step 4 you call this:
this.getUploadToken()
While in step 5
getUploadToken = (image) => {
image is then used as body.
Did you check your network tab while testing? It looks like you would be receiving a 400 error.

How to upload photos with React Native

node v10.14.1
npm v6.4.1
strapi v3.0.0#13.0.1
I'm trying to upload a picture taken with a "PhotoUpload" component to Strapi.
Despite various tests, I get an error 500 from the server.
Insert_Files_Into_DataBase = () => {
const formdata = new FormData();
formdata.append("files:", this.state.image1); //Base64
this.setState(
{
ActivityIndicator_Loading: true
},
() => {
fetch("" + NETWORK.SERVER_IP + ":1337/upload", {
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + this.props.userToken.jwt
},
body: formdata
})
.then(response => response.json())
.then(responseJsonFromServer => {
alert(responseJsonFromServer + "Image1 OK!");
this.setState({ ActivityIndicator_Loading: false });
})
.catch(error => {
console.error(error);
this.setState({ ActivityIndicator_Loading: false });
});
}
);
};
My "PhotoUpload" component allows me to retrieve the Base64 from the image. But it doesn't seem to work.
With Postman, everything works correctly
Are you doing this on the iOS simulator? I find uploading only works for me on a real iOS device.
In your code above, you are appending files: (with extra colon (:)). I think, you should append just files in the FormData() as:
formdata.append("files", this.state.image1); //Base64
This might be the case you are getting 500. If not also, you should append files instead of files:.
If this solves your problem, don't forget to hit upvote. :)
I found a first mistake!
I had simply forgotten the "HTTP://" in the address of my server.
Now, the server sends me back "true" but the image is not actually uploaded

React Native how to upload multiple photos to server at once

I've tried to upload multiple images to server at a once by using fetch.
Here is my code chip.
chooseImage(){
ImagePicker.openPicker({
multiple: true,
waitAnimationEnd: false
}).then(images => {
var photos = []
images.map((image, i) => {
photos.push({
uri:image.path,
name: image.name,
type: 'image/jpg'
})
let source = {uri: image.path}
this.state.photos.push({image: source, check: true, hairstyle: '', price: ''})
})
var data = new FormData();
data.append('photos', photos)
const config = {
method: 'POST',
headers: {
'Accept':'application/json',
'Content-Type':'multipart/form-data;',
'Authorization': this.props.auth.token
},
body: data,
}
fetch("http://**.***.***.***/api/providers/uploadPhotos", config)
.then(res=>res.json()).then((res) => {
console.log("----------Response----------")
console.log(res)
this._getStylist()
this.setState({photo_take: false});
}).catch(err=>{
console.log("------------Error-----------")
console.log(err)
console.log("error in uploading image")
}).done()
}).catch(e => {
console.log(e);
});
}
When I try it, I get 200 response from server and photos doesn't upload to server actually.
I searched the solution a few days, but couldn't find the suitable solution.
Thanks for any suggestion.
I am new at react native but seems like you can loop throght images array to upload images Maybe you can add this post actions in promise array then you can be sure that every photo added to your storage. I use promise array methods while working on my node js server project its very useful.
Edit for example:
var photos=[];
var promise_array=[];
photos.forEach(function(photo){
const config = {
method: 'POST',
headers: {
'Accept':'application/json',
'Content-Type':'multipart/form-data;',
'Authorization': this.props.auth.token
},
body: photo,
}
promise_array.push(fetch("http://**.***.***.***/api/providers/uploadPhotos", config)
.then(res=>res.json()).then((res) => {
console.log("----------Response----------")
console.log(res)
this._getStylist()
this.setState({photo_take: false});
}).catch(err=>{
console.log("------------Error-----------")
console.log(err)
console.log("error in uploading image")
}).done())
})
Promise.all(promise_array);
Since fetch is a promise I can put in promise array. Maybe there is syntax error in example and I am not sure react native support promise methods fully but here is documentation for promise method I use https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all