Upload to S3 from React Native with AWS Amplify - image

I'm trying to upload an image to S3 from React Native using Amplify. I am able to upload a text file SUCCESSFULLY. But not an image.
Here is my code:
import React from 'react';
import {View, Text, Button, Image} from 'react-native';
import {identityPoolId, region, bucket} from '../auth';
import image from '../assets/background.png';
import Amplify, {Storage} from 'aws-amplify';
Amplify.configure({
Auth: {identityPoolId,region},
Storage : {bucket,region}
})
const upload = () => {
Storage.put('logo.jpg', image, {contentType: 'image/jpeg'})
.then(result => console.log('result from successful upload: ', result))
.catch(err => console.log('error uploading to s3:', err));
}
const get = () => { //this works for both putting and getting a text file
Storage.get('amir.txt')
.then(res => console.log('result get', res))
.catch(err => console.log('err getting', err))
}
export default function ImageUpload(props) {
return (
<View style={{alignItems : 'center'}}>
<Image style={{width: 100, height: 100}} source={image} />
<Text>Click button to upload above image to S3</Text>
<Button title="Upload to S3" onPress={upload}/>
<Button title="Get from S3" onPress={get}/>
</View>
)
}
the error message is:
error uploading to s3: [Error: Unsupported body payload number]

I ended up using the react-native-aws3 library to upload the images to S3.
I wish it could be more straight forward to find answers with how to upload an image directly using AWS amplify, but it wasn't working. So here is what I did:
(the wrapper of this function is a React Component. I'm using ImagePicker from 'expo-image-picker', Permissions from 'expo-permissions' and Constants from 'expo-constants' to set up the Image uploading from the Camera Roll)
import {identityPoolId, region, bucket, accessKey, secretKey} from '../auth';
import { RNS3 } from 'react-native-aws3';
async function s3Upload(uri) {
const file = {
uri,
name : uri.match(/.{12}.jpg/)[0],
type : "image/png"
};
const options = { keyPrefix: "public/", bucket, region,
accessKey, secretKey, successActionStatus: 201}
RNS3.put(file, options)
.progress(event => {
console.log(`percentage uploaded: ${event.percent}`);
})
.then(res => {
if (res.status === 201) {
console.log('response from successful upload to s3:',
res.body);
console.log('S3 URL', res.body.postResponse.location);
setPic(res.body.postResponse.location);
} else {
console.log('error status code: ', res.status);
}
})
.catch(err => {
console.log('error uploading to s3', err)
})
}
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes : ImagePicker.MediaTypeOptions.All,
allowsEditing : true,
aspect : [4,3],
quality : 1
});
console.log('image picker result', result);
if (!result.cancelled) {
setImage(result.uri);
s3Upload(result.uri);
}
}

In our recent Single Page Application (SPA) style web application, from React, we used "S3 Signed URLs" for efficient upload/download of files and I felt this has resulted in a cleaner design as compared to direct upload/download.
What is the back-end services implemented in?

You can do it on your node server, save it the URL and send it back to the app.
It'll look like:
const AWS = require('aws-sdk');
var s3 = new AWS.S3({accessKeyId:'XXXXXXXXXXXX', secretAccessKey:'YYYYYYYYYYYY', region:'REGION'});
var params = {Bucket: 'yourBucket', Key: 'your_image/your_image.jpg', ContentType: 'image/jpeg'};
s3.getSignedUrl('putObject', params, function (err, url) {
// send it back to the app
});
On the app side you'll have something like:
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
Alert.aler("Upload", "Done");
} else {
Alert.aler("Upload", "Failed");
}
}
}
xhr.open('PUT', presignedUrlReceiveFromServer)
xhr.setRequestHeader('Content-Type', fileType)
xhr.send({ uri: imageFilePath, type: fileType, name: imageFileName })
Hope it helps

Related

Next.js: Importing images into a component

I am using "next": "^9.4.4",
And have : "next-images": "^1.4.0", "next-optimized-images": "^2.6.1",
And this is my next-config.js
const withCSS = require('#zeit/next-css');
const withSass = require('#zeit/next-sass');
const withImages = require('next-images');
const optimizedImages = require('next-optimized-images');
module.exports = withImages(
optimizedImages(
withCSS(
withSass({
target: 'serverless',
env: {
MAPBOX_ACCESS_TOKEN:
'TK421'
},
webpack(config, options) {
config.module.rules.push({
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader',
options: {
limit: 100000,
target: 'serverless'
}
}
});
return config;
}
})
)
)
);
But in my component I get a broken image link, This is my component:
import { useState, useEffect } from 'react';
import { Card, Icon, Image, Segment, Form } from 'semantic-ui-react';
import axios from 'axios';
function ImageUploader({ userAvatar }) {
var [userAvatar, setUserAvatar] = useState(userAvatar);
useEffect(() => {
setUserAvatar(userAvatar);
}, [userAvatar]);
function fileUploader(e) {
console.log('event fileUploader ', e);
var imageFormObj = new FormData();
console.log('e.target.files[0] ', e.target.files[0]);
imageFormObj.append('imageName', 'multer-image-' + Date.now());
imageFormObj.append('imageData', e.target.files[0]);
setUserAvatar(window.URL.createObjectURL(e.target.files[0]));
console.log('userAvatar ', userAvatar);
console.log('imageFormObj ', imageFormObj);
axios
.post(`/users/uploadmulter`, imageFormObj)
.then(data => {
if (data.data.success) {
alert('Image has been successfully uploaded using multer');
}
})
.catch(err => {
alert('Error while uploading image using multer');
});
}
return (
<>
<Image
src={require('../../public/static/uploads/profile-avatars/placeholder.jpg')}
alt="user-avatar"
/>
</>
);
}
I am confused too because in the docs it seems to indicate static files/images are supported outta the box...
Static File Serving
Next.js can serve static files, like images, under a folder called public in the root directory. Files inside public can then be referenced by your code starting from the base URL (/).
For example, if you add an image to public/my-image.png, the following code will access the image:
I've tried as they recommend:
<Image src="/uploads/profile-avatars/placeholder.jpg" alt="user-avatar" />
And the funny thing I am not getting a 404 in the browser!
Any help would be appreciated!
Your image must be located in a folder with the reserved name public. Then the offer from the box will work.

BeforeUpload do not trigger upload on promise resolved

Using React, and antd
I have the following code in my component:
<Upload
action={HttpService.getBaseUrl(`post_import_csv`, HttpService.AuditcoreAPIBasePath)}
headers={{"Authorization": `Bearer ${AuthHelper.getAuthKey()}`}}
showUploadList={false}
multiple={false}
beforeUpload={(file: RcFile): PromiseLike<any> => {
this.setCSV(file);
return new Promise((resolve) => {
this.state.requestUpload.pipe(take(1)).subscribe(() => {
resolve(file);
console.log('resolved')
});
})
}}></Upload>
Basically I want my beforeUpload to wait for the user to click on a button before uploading the file. I did so by returning a Promise and waiting for a rxjs Suject that is triggered on button click to resolve the promise. Pretty much following the doc
Here is the button code :
<Button
onClick={(e): void => {
this.state.requestUpload.next(true);
}}
>
Upload
</Button>
It works nice, but the file is never uploaded, I do see my log resolved but there is no trace of network call in my console.
I fixed using this approach which is cleaner :
https://codesandbox.io/s/xvkj90rwkz
Basically, having a custom function that handle upload. It doesn't explain why my tricky solution was not working, but I got it working.
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Upload, Button, Icon, message } from 'antd';
import reqwest from 'reqwest';
class Demo extends React.Component {
state = {
fileList: [],
uploading: false,
};
handleUpload = () => {
const { fileList } = this.state;
const formData = new FormData();
fileList.forEach(file => {
formData.append('files[]', file);
});
this.setState({
uploading: true,
});
// You can use any AJAX library you like
reqwest({
url: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
method: 'post',
processData: false,
data: formData,
success: () => {
this.setState({
fileList: [],
uploading: false,
});
message.success('upload successfully.');
},
error: () => {
this.setState({
uploading: false,
});
message.error('upload failed.');
},
});
};
render() {
const { uploading, fileList } = this.state;
const props = {
onRemove: file => {
this.setState(state => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
};
});
},
beforeUpload: file => {
this.setState(state => ({
fileList: [...state.fileList, file],
}));
return false;
},
fileList,
};
return (
<div>
<Upload {...props}>
<Button>
<Icon type="upload" /> Select File
</Button>
</Upload>
<Button
type="primary"
onClick={this.handleUpload}
disabled={fileList.length === 0}
loading={uploading}
style={{ marginTop: 16 }}
>
{uploading ? 'Uploading' : 'Start Upload'}
</Button>
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('container'));

How to save and display and image in react-native?

I have a question in react-native. Im using a module called "react-native-image-picker" to pick an image and display it on my app.
Now what i want is to store it somewhere (database, or local storage) and when i open again the app, the image that i choosed should be there. But i dont know what is the best option to do it.
I've already tryied to read some stuff like react-native-fs and fetch-blob but it doesnt help me, i guess.
What is the best option to do it?
Thank you.
First, renders view according to condition. For example if image is available then simply display the image else display TouchableOpacity which will help use to select pictures :
import React, { Component } from React;
import { View, TouchableOpacity, Text, Image } from 'react-native';
import ImagePicker from 'react-native-image-picker';
import AsyncStorage from '#react-native-community/async-storage';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isImageAvailable: false,
profilePic: null
}
}
componentDidMount = () => {
this.getImage();
}
getImage = async () => {
const profilePic = await AsyncStorage.getItem("profilePic");
if (profilePic) {
this.setState({
isImageAvailable: true,
profilePic: JSON.parse(profilePic)
});
}
}
selectProfilePic = () => {
const options = {
title: 'Select Avatar',
storageOptions: {
skipBackup: true,
path: 'images',
},
};
ImagePicker.showImagePicker(options, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
const source = { uri: response.uri };
// You can also display the image using data:
// const source = { uri: 'data:image/jpeg;base64,' + response.data };
AsyncStorage.setItem("profilePic", JSON.stringify(source));
this.setState({
profilePic: source,
isImageAvailable: true
});
}
});
}
render() {
return (
<View>
{
this.state.isImageAvailable && (
<Image source={this.state.profilePic} style={{ width: 200, height: 200 }} />
)
}
{
!this.state.isImageAvailable && (
<TouchableOpacity onPress={this.selectProfilePic}>
<Text>Choose Profile Pic</Text>
</TouchableOpacity>
)
}
</View>
)
}
}
Hope it will help you.
You can use realmdb as an aternative to Asyncstorage.

how to upload image in react-admin graphql

i saw example dataprovider in react-admin docs that is handle image upload and convert it to Base64 but i cant use this for graphql dataprovider anyone can help me? actually i want piece of code that handle image upload in react-admin graphql dataprovider
You have to create another client to convert images (or files) to base64 and wrap your graphQL client around it. Like this one:
upload.js
import { CREATE, UPDATE } from 'react-admin'
/**
* Convert a `File` object returned by the upload input into a base 64 string.
* That's not the most optimized way to store images in production, but it's
* enough to illustrate the idea of data provider decoration.
*/
const convertFileToBase64 = file =>
new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file.rawFile)
reader.onload = () => resolve(reader.result)
reader.onerror = reject
})
export default dataProvider => (fetchType, resource, params) => {
if (resource === 'Photo' && (fetchType === CREATE || fetchType === UPDATE)) {
const { data, ...rest_params } = params
return convertFileToBase64(data.data).then(base64 => {
return dataProvider(fetchType, resource, {
...rest_params,
data: { ...data, data: base64 }
})
})
}
return dataProvider(fetchType, resource, params)
}
data.js
import buildGraphQLProvider, { buildQuery } from 'ra-data-graphql-simple'
import { createHttpLink } from 'apollo-link-http'
import { ApolloLink } from 'apollo-link'
import { onError } from 'apollo-link-error'
import { AUTH_KEY } from '../authentication'
import { data } from '../schema'
const customBuildQuery = introspection => (fetchType, resource, params) => {
return buildQuery(introspection)(fetchType, resource, params)
}
const httpLink = createHttpLink({ uri: process.env.REACT_APP_GRAPHQL_URI })
const middlewareLink = new ApolloLink((operation, forward) => {
operation.setContext({
headers: {
Authorization: `Bearer ${localStorage.getItem(AUTH_KEY)}` || null
}
})
return forward(operation)
})
const errorLink = onError(({ networkError }) => {
if (networkError.statusCode === 401) {
// logout();
}
})
const link = middlewareLink.concat(httpLink, errorLink)
export default () =>
buildGraphQLProvider({
clientOptions: {
link: link
},
introspection: { schema: data.__schema },
buildQuery: customBuildQuery
})
App.js
import React, { Component } from 'react'
import { Admin, Resource } from 'react-admin'
import buildGraphQLProvider from 'data'
import addUploadCapabilities from 'upload'
import dashboard from 'dashboard'
import User from 'resources/User'
import Event from 'resources/Event'
import Photo from 'resources/Photo'
class App extends Component {
state = { dataProvider: null }
componentDidMount () {
buildGraphQLProvider().then(dataProvider => this.setState({ dataProvider }))
}
render () {
const { dataProvider } = this.state
if (!dataProvider) {
return (
<div className='loader-container'>
<div className='loader'>Loading...</div>
</div>
)
}
return (
<Admin
dashboard={dashboard}
title='Admin'
dataProvider={addUploadCapabilities(dataProvider)}
>
<Resource name='User' {...User} />
<Resource name='Event' {...Event} />
<Resource name='Photo' {...Photo} />
</Admin>
)
}
}
export default App

How to upload a base64 image to S3 with amplify, react native application

Looking for a simple example of how to upload a base64 image to aws S3 using amplify.
Assuming you configured Amplify Storage and set the permissions to public, here is a code example that uses Storage from Amplify to upload images to S3 bucket. The images are fetched from the local device using ImagePicker from Expo.
import React from 'react';
import {
StyleSheet,
ScrollView,
Image,
Dimensions } from 'react-native'
import { ImagePicker, Permissions } from 'expo'
import { Icon } from 'native-base'
import Amplify from '#aws-amplify/core'
import Storage from '#aws-amplify/storage'
import config from './aws-exports'
class App extends React.Component {
state = {
image: null,
}
// fetch a single image from user's device and save it to S3
useLibraryHandler = async () => {
await this.askPermissionsAsync()
let result = await ImagePicker.launchImageLibraryAsync(
{
allowsEditing: false,
//aspect: [4, 3],
}
)
console.log(result);
if (!result.cancelled) {
this.setState({ image: result.uri })
this.uploadImage(this.state.image)
}
}
// add a single image to S3
uploadImage = async uri => {
const response = await fetch(uri)
const blob = await response.blob() // format the data for images
const folder = 'images'
const fileName = 'flower.jpeg'
await Storage.put(folder + '/' + fileName, blob, {
contentType: 'image/jpeg',
level: 'public'
}).then(data => console.log(data))
.catch(err => console.log(err))
}
render() {
let { image } = this.state
let {height, width} = Dimensions.get('window')
return (
<ScrollView style={{flex: 1}} contentContainerStyle={styles.container}>
<Icon
name='md-add-circle'
style={styles.buttonStyle}
onPress={this.useLibraryHandler}
/>
{/*
true && expression always evaluates to expression,
and false && expression always evaluates to false
*/}
{image &&
<Image source={{ uri: image }} style={{ width: width, height: height/2 }} />
}
</ScrollView>
);
}
}
The name of the image is hardcoded which is not good. But this is a very good start nonetheless.
This is a simple method for uploading multiple images. It should work for single image too.
import {Storage} from "aws-amplify";
UploadPhotos(SelectedImages) {
SelectedImages.forEach(async (element) => {
let name = element.filename;
let access = { level: "protected", contentType: "image/jpeg" };
let imageData = await fetch(element.uri);
let blobData = await imageData.blob();
try {
Storage.put(name, blobData, access);
} catch (err) {
console.log("UploadPhotos error: ", err);
}
});
}

Resources