Uploading Images in React Native

Introduction

Throughout this series of tutorials we’ll walk through how to upload images when using React Native.

To follow along you’ll need to have React Native installed on your machine. Be sure to select the “Building Projects with Native Code” tab.

Once you’ve accomplish that, you’ll need to initialize a new React Native project.

Configuring Permissions

Info.plist

<key>NSPhotoLibraryUsageDescription</key>
<string>For choosing a photo.</string>
<key>NSCameraUsageDescription</key>
<string>For taking a photo.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>For saving a photo.</string>

AndroidManifest.xml

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Installing React Native Image Picker

Commands to run:

yarn add react-native-image-picker
react-native link react-native-image-picker

ImageUploadExample/App.js

import React from 'react'
import { View, Text, Image, Button } from 'react-native'
import ImagePicker from 'react-native-image-picker'

export default class App extends React.Component {
  state = {
    photo: null,
  }

  handleChoosePhoto = () => {
    const options = {
      noData: true,
    }
    ImagePicker.launchImageLibrary(options, response => {
      if (response.uri) {
        this.setState({ photo: response })
      }
    })
  }

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

Basic Node.js Server to Accept Images

Terminal

mkdir server
npm init
yarn add express body-parser multer

server/index.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: 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(3000, () => {
  console.log('App running on http://localhost:3000')
})

Terminal

node index.js

Deploying Node Server to Now.sh

Terminal

npm install -g now

now.json

{
  "version": 1
}

package.json

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.3",
    "express": "^4.16.4",
    "multer": "^1.4.1"
  }
}

server/index.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, process.env.NODE_ENV === 'production' ? '/tmp' : './images')
  },
  filename(req, file, callback) {
    callback(null, `${file.fieldname}_${Date.now()}_${file.originalname}`)
  },
})

const upload = multer({ storage: 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(3000, () => {
  console.log('App running on http://localhost:3000')
})

Uploading with the Fetch API

ImageUploadExample/App.js

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

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

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

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

  return data
}

export default class App extends React.Component {
  state = {
    photo: null,
  }

  handleUploadPhoto = () => {
    fetch('http://localhost:3000/api/upload', {
      method: 'POST',
      body: createFormData(this.state.photo, { userId: '123' }),
    })
      .then(response => response.json())
      .then(response => {
        console.log('upload succes', response)
        alert('Upload success!')
        this.setState({ photo: null })
      })
      .catch(error => {
        console.log('upload error', error)
        alert('Upload failed!')
      })
  }

  handleChoosePhoto = () => {
    const options = {
      noData: true,
    }
    ImagePicker.launchImageLibrary(options, response => {
      if (response.uri) {
        this.setState({ photo: response })
      }
    })
  }

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

Track Upload Progress

ImageUploadExample/App.js

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

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

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

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

  return data
}

const uploadFileWithProgress = (url, opts = {}, onProgress) =>
  new Promise((res, rej) => {
    const xhr = new XMLHttpRequest()
    xhr.open(opts.method || 'get', url)

    Object.keys(opts.headers || {}).forEach(value => {
      xhr.setRequestHeader(value, opts.headers[value])
    })

    if (xhr.upload && onProgress) {
      xhr.upload.onprogress = onProgress
    }

    xhr.onload = e => {
      res(e.target.response)
    }
    xhr.onerror = rej

    xhr.send(opts.body)
  })

export default class App extends React.Component {
  state = {
    photo: null,
    progress: 0,
  }

  handleUploadPhoto = () => {
    /*
    fetch("http://localhost:3000/api/upload", {
      method: "POST",
      body: createFormData(this.state.photo, { userId: "123" })
    })
      .then(response => response.json())
      .then(response => {
        console.log("upload succes", response);
        alert("Upload success!");
        this.setState({ photo: null });
      })
      .catch(error => {
        console.log("upload error", error);
        alert("Upload failed!");
      });
    */
    uploadFileWithProgress(
      'http://localhost:3000/api/upload',
      {
        method: 'POST',
        body: createFormData(this.state.photo, { userId: '123' }),
      },
      event => {
        const progress = Math.floor((event.loaded / event.total) * 100)
        console.log('progress', progress)
        this.setState({ progress })
      }
    )
      .then(response => {
        console.log('upload succes', response)
        alert('Upload success!')
        this.setState({ photo: null, progress: 0 })
      })
      .catch(error => {
        console.log('upload error', error)
        alert('Upload failed!')
      })
  }

  handleChoosePhoto = () => {
    const options = {
      noData: true,
    }
    ImagePicker.launchImageLibrary(options, response => {
      if (response.uri) {
        this.setState({ photo: response })
      }
    })
  }

  render() {
    const { photo, progress } = this.state
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        {photo && (
          <React.Fragment>
            <Image
              source={{ uri: photo.uri }}
              style={{ width: 300, height: 300 }}
            />
            <Text>
              Progress:
              {progress}%
            </Text>
            <Button title="Upload Photo" onPress={this.handleUploadPhoto} />
          </React.Fragment>
        )}
        <Button title="Choose Photo" onPress={this.handleChoosePhoto} />
      </View>
    )
  }
}

Upload Multiple Images

ImageUploadExample/App.js

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

const createFormData = (photos, body) => {
  const data = new FormData()

  photos.forEach(photo => {
    data.append('photo', {
      name: photo.fileName,
      type: photo.type,
      uri:
        Platform.OS === 'android'
          ? photo.uri
          : photo.uri.replace('file://', ''),
    })
  })

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

  return data
}

const uploadFileWithProgress = (url, opts = {}, onProgress) =>
  new Promise((res, rej) => {
    const xhr = new XMLHttpRequest()
    xhr.open(opts.method || 'get', url)

    Object.keys(opts.headers || {}).forEach(value => {
      xhr.setRequestHeader(value, opts.headers[value])
    })

    if (xhr.upload && onProgress) {
      xhr.upload.onprogress = onProgress
    }

    xhr.onload = e => {
      res(e.target.response)
    }
    xhr.onerror = rej

    xhr.send(opts.body)
  })

export default class App extends React.Component {
  state = {
    // photo: null,
    photos: [],
    progress: 0,
  }

  handleUploadPhoto = () => {
    /*
    fetch("http://localhost:3000/api/upload", {
      method: "POST",
      body: createFormData(this.state.photo, { userId: "123" })
    })
      .then(response => response.json())
      .then(response => {
        console.log("upload succes", response);
        alert("Upload success!");
        this.setState({ photo: null });
      })
      .catch(error => {
        console.log("upload error", error);
        alert("Upload failed!");
      });
    */
    uploadFileWithProgress(
      'http://localhost:3000/api/upload',
      {
        method: 'POST',
        body: createFormData(this.state.photos, { userId: '123' }),
      },
      event => {
        const progress = Math.floor((event.loaded / event.total) * 100)
        console.log('progress', progress)
        this.setState({ progress })
      }
    )
      .then(response => {
        console.log('upload succes', response)
        alert('Upload success!')
        this.setState({
          // photo: null,
          photos: [],
          progress: 0,
        })
      })
      .catch(error => {
        console.log('upload error', error)
        alert('Upload failed!')
      })
  }

  handleChoosePhoto = () => {
    const options = {
      noData: true,
    }
    ImagePicker.launchImageLibrary(options, response => {
      if (response.uri) {
        // this.setState({ photo: response });
        this.setState(state => ({
          photos: [...state.photos, response],
        }))
      }
    })
  }

  render() {
    // const { photo, progress } = this.state;
    const { photos, progress } = this.state
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        {photos.map(photo => (
          <Image
            key={photo.uri}
            source={{ uri: photo.uri }}
            style={{ width: 300, height: 300 }}
          />
        ))}
        {photos.length > 0 && (
          <React.Fragment>
            <Text>
              Progress:
              {progress}%
            </Text>
            <Button title="Upload Photo" onPress={this.handleUploadPhoto} />
          </React.Fragment>
        )}
        <Button title="Choose Photo" onPress={this.handleChoosePhoto} />
      </View>
    )
  }
}

Simulating a Slow Network

Handling a Slow Network

ImageUploadExample/App.js

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

const createFormData = (photos, body) => {
  const data = new FormData()

  photos.forEach(photo => {
    data.append('photo', {
      name: photo.fileName,
      type: photo.type,
      uri:
        Platform.OS === 'android'
          ? photo.uri
          : photo.uri.replace('file://', ''),
    })
  })

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

  return data
}

const uploadFileWithProgress = (url, opts = {}, onProgress) =>
  new Promise((res, rej) => {
    const xhr = new XMLHttpRequest()
    xhr.open(opts.method || 'get', url)

    Object.keys(opts.headers || {}).forEach(value => {
      xhr.setRequestHeader(value, opts.headers[value])
    })

    if (xhr.upload && onProgress) {
      xhr.upload.onprogress = onProgress
    }

    xhr.timeout = 10000
    xhr.ontimeout = rej

    xhr.onload = e => {
      res(e.target.response)
    }
    xhr.onerror = rej

    xhr.send(opts.body)
  })

export default class App extends React.Component {
  state = {
    // photo: null,
    photos: [],
    progress: 0,
  }

  handleUploadPhoto = () => {
    /*
    fetch("http://localhost:3000/api/upload", {
      method: "POST",
      body: createFormData(this.state.photo, { userId: "123" })
    })
      .then(response => response.json())
      .then(response => {
        console.log("upload succes", response);
        alert("Upload success!");
        this.setState({ photo: null });
      })
      .catch(error => {
        console.log("upload error", error);
        alert("Upload failed!");
      });
    */
    uploadFileWithProgress(
      'https://server-zhilrktcgu.now.sh/api/upload',
      {
        method: 'POST',
        body: createFormData(this.state.photos, { userId: '123' }),
      },
      event => {
        const progress = Math.floor((event.loaded / event.total) * 100)
        console.log('progress', progress)
        this.setState({ progress })
      }
    )
      .then(response => {
        console.log('upload succes', response)
        alert('Upload success!')
        this.setState({
          // photo: null,
          photos: [],
          progress: 0,
        })
      })
      .catch(error => {
        console.log('upload error', error)
        alert('Upload failed!')
      })
  }

  handleChoosePhoto = () => {
    const options = {
      noData: true,
      maxWidth: 500,
    }
    ImagePicker.launchImageLibrary(options, response => {
      if (response.uri) {
        // this.setState({ photo: response });
        this.setState(state => ({
          photos: [...state.photos, response],
        }))
      }
    })
  }

  render() {
    // const { photo, progress } = this.state;
    const { photos, progress } = this.state
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        {photos.map(photo => (
          <Image
            key={photo.uri}
            source={{ uri: photo.uri }}
            style={{ width: 300, height: 300 }}
          />
        ))}
        {photos.length > 0 && (
          <React.Fragment>
            <Text>
              Progress:
              {progress}%
            </Text>
            <Button title="Upload Photo" onPress={this.handleUploadPhoto} />
          </React.Fragment>
        )}
        <Button title="Choose Photo" onPress={this.handleChoosePhoto} />
      </View>
    )
  }
}