Updated December 11, 2018
How to Upload Images in a React Native App
Originally publish on medium.com.
Just a heads up that we have a full class on uploading images in React Native here on React Native School! I've also
Images add another dimension to a mobile app. User-generated images amplify that.
If you’re building a React Native app, how do you give your users the ability to upload images? React Native has polyfills for many web APIs — to do image uploading we’ll be using the fetch
API.
Setup
Before we can actually start uploading images, we’ll to create two projects:
- A React Native app
- A basic node server (so we have somewhere to upload the photo to)
React Native App
Note: This tutorial assumes you already have React Native installed on your machine.
We‘ll be doing a few things here:
- Creating a directory for both of our apps to live in
- Creating a new React Native app
- Installing the
react-native-image-picker
library - Linking the native dependencies of
react-native-image-picker
In your terminal, run the following:
Terminal
mkdir image-upload-example
cd image-upload-example
react-native init mobile
cd mobile
npm install --save react-native-image-picker
react-native link react-native-image-picker
You then have to enable the necessary permissions in both the iOS and Android apps associated with your React Native app.
On iOS, this takes place in mobile/ios/mobile/Info.plist
, and you’ll want to add:
mobile/ios/mobile/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>
On Android, you’ll want to find the AndroidManifest.xml
file a few directories down in mobile/android
and add:
mobile/android/AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
More info on permissions and why they are needed can be found here.
You can then run react-native run-ios
or react-native run-android
, resulting in something similar to the following:
Node Server
In a new terminal window, run the following commands:
Terminal
cd image-upload-example
mkdir server
cd server
mkdir images
npm init // answer the questions
npm install --save express body-parser multer
touch index.js
Then in index.js
, put the following:
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');
});
Going over everything related to the server here is beyond scope of this tutorial. If you’d like to learn more about it please watch this video where I go into more details on it.
Finally, you can run the app with node index.js
.
Now we’re ready to start!
Choosing An Image
Before we can upload an image, we need to have an image to upload! That’s where react-native-image-picker
comes in. It’ll allow us to select an image from our device.
Looking for a version of this using hooks? Don't fret! Follow this tutorial on how to convert a fetch request from classes to hooks!
You can update mobile/App.js
with the following:
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>
);
}
}
This code allows the user to open the image gallery and select a photo. Once they select a photo it’ll be displayed in the app, like this:
Creating the Request Body
If you’ve used the fetch
API before to create/update any data, you’ve likely done something like:
example.js
fetch('MY_API_URL', {
method: 'POST',
body: JSON.stringify({
userId: '123',
}),
});
To upload an image, we instead need to upload data in accordance with a multipart/form-data
encoding type.
To do this, we can generate the body of our request using FormData
.
When we set up our server, we told it the image would exist in the photo
key, so we need to make sure that’s where we put all the required photo information. We’ll also allow you to pass any other associated information (like userId).
App.js
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;
};
First we initialize FormData
, and we then append the photo
key. The body of this message is the minimum required to get this working. We need to pass a file name, a file type, and then a uri. The uri is where the image is located on the device.
You’ll notice that we need to massage this uri a bit based on platform to make it work. Essentially, it just boils down to stripping file://
from the uri on iOS.
Finally, we loop over any additional data (that’s not the photo) that we want to pass on to the endpoint.
Uploading the Image
Finally, we can upload the photo.
Quick Note: If you’re using Android or a physical iOS device, you’ll need to replace
localhost
with your computer’s IP address.
We’ll create a new function on our component to upload the photo.
App.js
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!');
});
};
Our endpoint is at api/upload
and expects a POST
request. We then pass the photo we saved to state previously to the createFormData
function.
We can then tap into the promise chain that fetch
provides us. First we convert the response to a json object and then alert the user the photo has been uploaded!
If the image fails to upload, we catch
that error and alert the user the image failed to upload.
Before we can actually upload the photo, we need to give a user a button they can tap to start the process.
App.js
export default class App extends React.Component {
...
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" onPress=[this.handleUpload} />
</React.Fragment>
)}
<Button title="Choose Photo" onPress={this.handleChoosePhoto} />
</View>
)
}
}
Now, once you select a photo and press upload, you should see a new image appear on the server in server/images
.
Looking for more on working with images in React Native? Here's a list of all our image related React Native lessons