javascript

fetch-api

react-native

Hi just learn to use js and react-native. I cant use FormData it always shows unsupported bodyinit type. I want to send text rather then JSON.stringify. Can anyone help me? Thanks!

var data = new FormData()
data.append('haha', 'input')

fetch('http://www.mywebsite.com/search.php', { 
  method: 'post',
  body: data
})
.then((response) => response.json())
.then((responseData) => {
  console.log('Fetch Success==================');
  console.log(responseData);

  var tempMarker = [];
  for (var p in responseData) {
   tempMarker.push({
    latitude: responseData[p]['lat'],
    longitude: responseData[p]['lng'] 
   })
  }

  this.setState({
    marker: tempMarker
  });

})
.catch((error) => {
  console.warn(error);
})
.done();

Solution 1

Here is my simple code FormData with react-native to post request with string and image.

I have used react-native-image-picker to capture/select photo react-native-image-picker

let photo = { uri: source.uri}
let formdata = new FormData();

formdata.append("product[name]", 'test')
formdata.append("product[price]", 10)
formdata.append("product[category_ids][]", 2)
formdata.append("product[description]", '12dsadadsa')
formdata.append("product[images_attributes[0][file]]", {uri: photo.uri, name: 'image.jpg', type: 'image/jpeg'})

NOTE you can change image/jpeg to other content type. You can get content type from image picker response.

fetch('http://192.168.1.101:3000/products',{
  method: 'post',
  headers: {
    'Content-Type': 'multipart/form-data',
  },
  body: formdata
  }).then(response => {
    console.log("image uploaded")
  }).catch(err => {
    console.log(err)
  })  
});

Solution 2

This worked for me

var serializeJSON = function(data) {
  return Object.keys(data).map(function (keyName) {
    return encodeURIComponent(keyName) + '=' + encodeURIComponent(data[keyName])
  }).join('&');
}

var response = fetch(url, {
  method: 'POST',
  body: serializeJSON({
    haha: 'input'
  })
});

Solution 3

If you want to set custom content-type for formData item:

var img = {
    uri : 'file://opa.jpeg',
    name: 'opa.jpeg',
    type: 'image/jpeg'
};
var personInfo = {
    name : 'David',
    age: 16
};

var fdata = new FormData();
fdata.append('personInfo', {
    "string": JSON.stringify(personInfo), //This is how it works :)
    type: 'application/json'
});

fdata.append('image', {
    uri: img.uri,
    name: img.name,
    type: img.type
});

Solution 4

Providing some other solution; we're also using react-native-image-picker; and the server side is using koa-multer; this set-up is working good:

ui

ImagePicker.showImagePicker(options, (response) => {
      if (response.didCancel) {}
      else if (response.error) {}
      else if (response.customButton) {}
      else {
        this.props.addPhoto({ // leads to handleAddPhoto()
          fileName: response.fileName,
          path: response.path,
          type: response.type,
          uri: response.uri,
          width: response.width,
          height: response.height,
        });
      }
    });

handleAddPhoto = (photo) => { // photo is the above object
    uploadImage({ // these 3 properties are required
      uri: photo.uri,
      type: photo.type,
      name: photo.fileName,
    }).then((data) => {
      // ...
    });
  }

client

export function uploadImage(file) { // so uri, type, name are required properties
  const formData = new FormData();
  formData.append('image', file);

  return fetch(`${imagePathPrefix}/upload`, { // give something like https://xx.yy.zz/upload/whatever
    method: 'POST',
    body: formData,
  }
  ).then(
    response => response.json()
  ).then(data => ({
    uri: data.uri,
    filename: data.filename,
  })
  ).catch(
    error => console.log('uploadImage error:', error)
  );
}

server

import multer from 'koa-multer';
import RouterBase from '../core/router-base';

const upload = multer({ dest: 'runtime/upload/' });

export default class FileUploadRouter extends RouterBase {
  setupRoutes({ router }) {
    router.post('/upload', upload.single('image'), async (ctx, next) => {
      const file = ctx.req.file;
      if (file != null) {
        ctx.body = {
          uri: file.filename,
          filename: file.originalname,
        };
      } else {
        ctx.body = {
          uri: '',
          filename: '',
        };
      }
    });
  }
}

Solution 5

Usage of formdata in react-native

I have used react-native-image-picker to select photo. In my case after choosing the photp from mobile. I'm storing it's info in component state. After, I'm sending POST request using fetch like below

const profile_pic = {
  name: this.state.formData.profile_pic.fileName,
  type: this.state.formData.profile_pic.type,
  path: this.state.formData.profile_pic.path,
  uri: this.state.formData.profile_pic.uri,
}
const formData = new FormData()
formData.append('first_name', this.state.formData.first_name);
formData.append('last_name', this.state.formData.last_name);
formData.append('profile_pic', profile_pic);
const Token = 'secret'

fetch('http://10.0.2.2:8000/api/profile/', {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "multipart/form-data",
      Authorization: `Token ${Token}`
    },
    body: formData
  })
  .then(response => console.log(response.json()))

Solution 6

You can use react-native-image-picker and axios (form-data)

uploadS3 = (path) => {
    var data = new FormData();

    data.append('files',
        { uri: path, name: 'image.jpg', type: 'image/jpeg' }
    );

    var config = {
        method: 'post',
        url: YOUR_URL,
        headers: {
            Accept: "application/json",
            "Content-Type": "multipart/form-data",
        },
        data: data,
    };

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

react-native-image-picker

selectPhotoTapped() {
    const options = {
        quality: 1.0,
        maxWidth: 500,
        maxHeight: 500,
        storageOptions: {
            skipBackup: true,
        },
    };

    ImagePicker.showImagePicker(options, response => {
        //console.log('Response = ', response);

        if (response.didCancel) {
            //console.log('User cancelled photo picker');
        } else if (response.error) {
            //console.log('ImagePicker Error: ', response.error);
        } else if (response.customButton) {
            //console.log('User tapped custom button: ', response.customButton);
        } else {
            let source = { uri: response.uri };
             
            // Call Upload Function
            this.uploadS3(source.uri)

            // You can also display the image using data:
            // let source = { uri: 'data:image/jpeg;base64,' + response.data };
            this.setState({
                avatarSource: source,
            });
            // this.imageUpload(source);
        }
    });
}

Solution 7

I was looking for a long time an answer that solve the problem and this is the way I did it

I take the file with expo-document-picker

const pickDocument = async (tDocument) => {
    let result = await DocumentPicker.getDocumentAsync();
    result.type = mimetype(result.name);
    if (result.type === undefined){
      alert("not allowed extention");
      return null;
    }
    let formDat = new FormData();
    formDat.append("file", result);
    uploadDoc(formDat);
  };

  const mimetype = (name) => {
    let allow =  {"png":"image/png","pdf":"application/json","jpeg":"image/jpeg", "jpg":"image/jpg"};
    let extention = name.split(".")[1];
    if (allow[extention] !== undefined){
      return allow[extention]
    }
    else {
      return undefined
    }
  }

const uploadDoc = (data) =>  {
    fetch("MyApi", {
      method: "POST",
      body: data
    }).then(res => res.json())
    .then(response =>{
      if (response.result === 1) {
        //somecode
      } else {
        //somecode
      }
    });
  }

this is because android doesn't manage the mime-type of your file so if you put away the header "Content-type" and instead you put the mime-type on the file it gonna send the correct header

works on IOS an Android

Solution 8

I am using Expo SDK 42 (react-native v0.63). And I was using the expo-document-picker library to pick the documents & upload to server.

I am assuming you want to use FormData to upload some kind of file.

This is the code I am using to open the picker & get the metadata about the file.

import * as DocumentPicker from 'expo-document-picker';

const result = await DocumentPicker.getDocumentAsync({
    type: 'image/*',
    copyToCacheDirectory: false,
    multiple: false,
});

if (result.type === 'success') {
    const name_split = result.name.split('.');
    const ext = name_split[name_split.length - 1];
    result.type = helper.get_mime_type(ext);
    delete result.size;
}

(You can write your function to get the mime type from the file extension or use some library from npm)

result has the following info at this point:

result.uri // "content://path/to/file.ext"
result.name // filename
result.type // the mime type of the file such as "image/png"

And this is the code I am using to upload the file to server using axios library:

const formdata = new FormData();
formdata.append('custom_param', 'value');
formdata.append('file', result); // 'result' is from previous code snippet

const headers = {
    accept: 'application/json',
    'content-type': 'multipart/form-data',
};

const opts = {
    method: 'POST',
    url: 'your backend endpoint',
    headers: headers,
    data: formdata,
};
return await axios.request(axiosopts);

Solution 9

I have used form data with ImagePicker plugin. and I got it working please check below code

ImagePicker.showImagePicker(options, (response) => {
  console.log('Response = ', response);
  if (response.didCancel) {
    console.log('User cancelled photo picker');
  }
  else if (response.error) {
    console.log('ImagePicker Error: ', response.error);
  }
  else if (response.customButton) {
    console.log('User tapped custom button: ', response.customButton);
  }
  else {
    fetch(globalConfigs.api_url+"/gallery_upload_mobile",{
      method: 'post',
      headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
      ,
    body: JSON.stringify({
      data: response.data.toString(),
      fileName: response.fileName
    })
      }).then(response => {
        console.log("image uploaded")
      }).catch(err => {
        console.log(err)
      })  
  }
});

Solution 10

If you are getting multipart: boundry not found error you can use fetch api instead of axios it works with fetch but not with axios for some reason.

fetch('http://192.168.10.38:8080/api/auth/register',{
 method: 'post',
 headers: {
   'Content-Type': 'multipart/form-data',
  },
 body: formdata
 }).then(response => {
    console.log("success")
 }).catch(err => {
    console.log(err)

})
});