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
- How to install Network Link Conditioner
- Linux: netem
- Windows: Fiddler
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>
)
}
}