How to Upload Images from React Native

Author

Spencer Carli

Developer, cat dad, and devout pizza lover. Teaching at React Native School and building apps with Handlebar Labs.

Last Updated: February 17, 2021

In this tutorial we'll cover how to upload an image from a React Native app to a remote server.

This is just a high level overview. If you want details (upload progress, setting up a server, multiple images, etc.) please check out our class on Uploading Images in React Native.

With that said, let's get into it.

Tools

  • react-native-image-picker. Allows us to access the library of images or the camera.
  • Express + multer: Create a server that can accept image uploads. However you setup the backend the process will be extremely similar.

The Code

Explanation of code below.

// server.js

const express = require('express');
const multer = require('multer');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

const storage = multer.diskStorage({
  destination(req, file, callback) {
    callback(null, './images');
  },
  filename(req, file, callback) {
    callback(null, `${file.fieldname}_${Date.now()}_${file.originalname}`);
  },
});

const upload = multer({ storage });

app.get('/', (req, res) => {
  res.status(200).send('You can post to /api/upload.');
});

app.post('/api/upload', upload.array('photo', 3), (req, res) => {
  console.log('file', req.files);
  console.log('body', req.body);
  res.status(200).json({
    message: 'success!',
  });
});

app.listen(process.env.PORT || 3000, () => {
  console.log(
    `server is running at http://localhost:${process.env.PORT || 3000}`
  );
});
// App.js

import React from 'react';
import { View, Image, Button, Platform } from 'react-native';
import { launchImageLibrary } from 'react-native-image-picker';

const SERVER_URL = 'http://localhost:3000';

const createFormData = (photo, body = {}) => {
  const data = new FormData();

  data.append('photo', {
    name: photo.fileName,
    type: photo.type,
    uri: Platform.OS === 'ios' ? photo.uri.replace('file://', '') : photo.uri,
  });

  Object.keys(body).forEach((key) => {
    data.append(key, body[key]);
  });

  return data;
};

const App = () => {
  const [photo, setPhoto] = React.useState(null);

  const handleChoosePhoto = () => {
    launchImageLibrary({ noData: true }, (response) => {
      // console.log(response);
      if (response) {
        setPhoto(response);
      }
    });
  };

  const handleUploadPhoto = () => {
    fetch(`${SERVER_URL}/api/upload`, {
      method: 'POST',
      body: createFormData(photo, { userId: '123' }),
    })
      .then((response) => response.json())
      .then((response) => {
        console.log('response', response);
      })
      .catch((error) => {
        console.log('error', error);
      });
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      {photo && (
        <>
          <Image
            source={{ uri: photo.uri }}
            style={{ width: 300, height: 300 }}
          />
          <Button title="Upload Photo" onPress={handleUploadPhoto} />
        </>
      )}
      <Button title="Choose Photo" onPress={handleChoosePhoto} />
    </View>
  );
};

export default App;

Explanation - server.js

The server sets the ground work for what we're doing so let's talk about that first. It's a standard Express server but our /api/upload path uses the Multer middleware and accepts multipart/form-data. If it sees a name with photo it's going to handle the image upload.

You can check out the multer documentation for details on how it's configured but this will just write files locally to the images/ directory.

The file(s) that were uploaded will be available at request.files and any other data passed will be available at request.body.

Explanation - App.js

Let's look at the handleChoosePhoto function first. In it we open the image library and when a user selects an image we store that in state.

Next is handleUploadPhoto. In it we set up a standard fetch request and set the method to POST, which will call the /api/post route we defined in the server.

What's different is how we pass data in the body field.

If you're anything like me you're used to simply stringifying some JSON (JSON.stringify({ example: true })) and calling it done.

To handle image uploads we need to set the encoding type to multipart/form-data which means we need to format our data differently.

Thus the createFormData function. This function will go ahead and take the image we selected and add it to the photo field of the form data with the required info.

It will also take any other data you want to pass to it and add it to the form data, as you can see from the userId.

With this the file will be available at request.files and the userId will be available at request.body.userId.

Important Note: Notice that ternary operator in the uri field? We have to do that because iOS prepends the path it returns with a file:// that I've found breaks file uploads so we remove it.

Conclusion

This was a super brief overview of a relatively complex subject. The reality is that uploading an image takes more time than a small JSON object and on mobile networks we have unique challenges to deal with such as:

  • Slow networks
  • No network connection
  • Network connections stopping & starting during upload

These aren't exceptions - they're guarantees that we have to deal with. That's why we cover them in the Uploading Images in React Native class here on React Native School.

If you're interested in going deeper on this subject (or any of the others we cover in our classes) become a member!

React Native School Logo

React Native School

Want to further level up as a React Native developer? Join React Native School! You'll get access to all of our classes and our private Slack community.

Join the email list to be notified of all new lessons and classes!