Convert local img to base64 with react hooks - reactjs

I am in React hooks trying to utilise code in this question Stackoverflow question- but, as I think someone is pointing out, there are problems implementing this in a local environment.
I have a variable containing my src path:
import avatar from '../../Assets/Photos/avatar.png'
Then I try to set the photo as a useState state:
const [ photo, setPhoto ] = useState(avatar)
Then I use useEffect to wait until the variable has loaded:
useEffect(() => {
if (photo) {
console.log("PHOTO EXISTS!!!!!!!!!!!!!!", photo)
console.log(convertImgToBase64())
}
}, [photo])
And the convertImgToBase64 function is almost identical to the one in the question I referenced:
function convertImgToBase64()
{
var canvas = document.createElement('CANVAS');
let img = document.createElement('avatar');
img.src = photo;
img.onload = function()
{
canvas.height = img.height;
canvas.width = img.width;
var dataURL = canvas.toDataURL('image/png');
canvas = null;
return dataURL
};
}
This returns undefined - why is this please?

Create an image state:
const[image, setImage] = useState("")
Function to convert to base64:
const convertBase64 = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.onerror = (error) => {
reject(error);
};
});
};
Set your image state to base64 string image:
const handleFileRead = async (event) => {
const file = event.target.files[0];
const base64 = await convertBase64(file);
setImage(base64);
};
<input type="file" multiple accept="image/gif, image/jpeg, image/png" onChange={(e) => handleFileRead(e)} />

Related

All files in an array not converting to base64 in react js

I have a custom hook called useBase64 where I convert my image to Base64. This hook accepts an initial value and returns handleCreateBase64 function and logo (piece of state). handleCreateBase64 converts the file to base64 and stores it in logo. I am sending an array of images as File and then mapping through that array and I want to store it in logo and then send it to that component. right now the problem is that in the logo I can only see 1 base64 even if I select multiple files.
useBase64 hook: -
import { useCallback, useState } from "react";
export const useBase64 = (initialValue) => {
const [logo, setLogo] = useState(initialValue)
const handleCreateBase64 = useCallback(async (receivedFile) => {
let file = receivedFile;
if(initialValue instanceof Array) {
file = Object.values(receivedFile)
file.map(async el => {
const base64 = await convertToBase64(el)
setLogo([...logo, base64])
})
}
});
const convertToBase64 = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
if (!file) {
console.log("no image");
} else {
fileReader.readAsDataURL(file);
fileReader.onload = () => {
resolve(fileReader.result);
};
}
fileReader.onerror = (error) => {
reject(error);
};
});
};
return { handleCreateBase64, logo }
}
The result: -
As you can see above that I have 3 images selected but I see only 1 base64. I am quiet new to this so it will be great if full explanation is provided.
Wrong use of map and setState
Try:
const items = await Promise.all(file.map(el => convertToBase64(el)))
setLogo([...logo, ...items])

How to add additional info to an image?

I am new to React and Ionic and builded the Ionic React photo gallery app with the help of this ionic tutorial. Now I also want to add additional info like its date, its location, some inputfields,... to the image when it is displayed but I just did not figure out how.
This is the code from the tutorial:
import { useState, useEffect } from "react";
import { isPlatform } from '#ionic/react';
import { Camera, CameraResultType, CameraSource, Photo } from '#capacitor/camera';
import { Filesystem, Directory } from '#capacitor/filesystem'
import { Storage } from '#capacitor/storage'
import { Capacitor } from '#capacitor/core';
import internal from "assert";
import { defaultMaxListeners } from "stream";
const PHOTO_STORAGE = "photos";
export function usePhotoGallery() {
const [photos, setPhotos] = useState<UserPhoto[]>([]);
useEffect(() => {
const loadSaved = async () => {
const {value} = await Storage.get({key: PHOTO_STORAGE });
const photosInStorage = (value ? JSON.parse(value) : []) as UserPhoto[];
// If running on the web...
if (!isPlatform('hybrid')) {
for (let photo of photosInStorage) {
const file = await Filesystem.readFile({
path: photo.filepath,
directory: Directory.Data
});
// Web platform only: Load the photo as base64 data
photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
}
}
setPhotos(photosInStorage);
};
loadSaved();
}, []);
const takePhoto = async () => {
const cameraPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100
});
const fileName = new Date().getTime() + '.jpeg';
const savedFileImage = await savePicture(cameraPhoto, fileName);
const newPhotos = [savedFileImage, ...photos];
setPhotos(newPhotos);
Storage.set({key: PHOTO_STORAGE,value: JSON.stringify(newPhotos)});
};
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
let base64Data: string;
// "hybrid" will detect Cordova or Capacitor;
if (isPlatform('hybrid')) {
const file = await Filesystem.readFile({
path: photo.path!
});
base64Data = file.data;
} else {
base64Data = await base64FromPath(photo.webPath!);
}
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data
});
if (isPlatform('hybrid')) {
// Display the new image by rewriting the 'file://' path to HTTP
// Details: https://ionicframework.com/docs/building/webview#file-protocol
return {
filepath: savedFile.uri,
webviewPath: Capacitor.convertFileSrc(savedFile.uri),
};
}
else {
// Use webPath to display the new image instead of base64 since it's
// already loaded into memory
return {
filepath: fileName,
webviewPath: photo.webPath
};
}
};
const deletePhoto = async (photo: UserPhoto) => {
// Remove this photo from the Photos reference data array
const newPhotos = photos.filter(p => p.filepath !== photo.filepath);
// Update photos array cache by overwriting the existing photo array
Storage.set({key: PHOTO_STORAGE, value: JSON.stringify(newPhotos) });
// delete photo file from filesystem
const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1);
await Filesystem.deleteFile({
path: filename,
directory: Directory.Data
});
setPhotos(newPhotos);
};
return {
deletePhoto,
photos,
takePhoto
};
}
export interface UserPhoto {
filepath: string;
webviewPath?: string;
}
export async function base64FromPath(path: string): Promise<string> {
const response = await fetch(path);
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = () => {
if (typeof reader.result === 'string') {
resolve(reader.result);
} else {
reject('method did not return a string')
}
};
reader.readAsDataURL(blob);
});
}
I managed to get the date and location with the Geolocation Plugin but I have no clue how to bind it to the taken photo...
I assume that I have to add the desired data via function base64FromPath() for web and const file = await Filesystem.readFile() for mobile? And afterwards display with the export interface UserPhoto part but I am completly lost and do not know where to start. Any suggestions welcome!
I found a way might not be the prettiest or directest but it seems to work for me.
Within the takePhoto function I get the location and date and then pass it on via the savePicture function. Inside the savePicture function I return latitude, longitude and time and therefore can access it through the loadSaved function within the photo object. Inside interface UserPhoto the 3 variables need also to be declared.
Here is the updated code if somebody would like to make a suggestion:
import { useState, useEffect } from "react";
import { isPlatform } from '#ionic/react';
import { Geolocation, Geoposition } from '#ionic-native/geolocation';
import { Camera, CameraResultType, CameraSource, CameraDirection, Photo } from '#capacitor/camera';
import { Filesystem, Directory } from '#capacitor/filesystem'
import { Storage } from '#capacitor/storage'
import { Capacitor } from '#capacitor/core';
import { stringify } from "querystring";
const PHOTO_STORAGE = "photos";
export function usePhotoGallery() {
const [photos, setPhotos] = useState<UserPhoto[]>([]);
useEffect(() => {
const loadSaved = async () => {
const {value} = await Storage.get({key: PHOTO_STORAGE });
const photosInStorage = (value ? JSON.parse(value) : []) as UserPhoto[];
// If running on the web...
if (!isPlatform('hybrid')) {
for (let photo of photosInStorage) {
const file = await Filesystem.readFile({
path: photo.filepath,
directory: Directory.Data
});
// Web platform only: Load the photo as base64 data
photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
}
}
setPhotos(photosInStorage);
};
loadSaved();
}, []);
const takePhoto = async () => {
const position = await Geolocation.getCurrentPosition();
const latitude = position.coords.latitude
const longitude = position.coords.longitude
const time = new Date(position.timestamp).toLocaleString()
const cameraPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
direction: CameraDirection.Rear,
quality: 100
});
const fileName = new Date().getTime() + '.jpeg';
const savedFileImage = await savePicture(cameraPhoto, fileName, latitude, longitude, time);
const newPhotos = [savedFileImage, ...photos];
setPhotos(newPhotos);
Storage.set({key: PHOTO_STORAGE,value: JSON.stringify(newPhotos)});
};
const savePicture = async (photo: Photo, fileName: string, latitude: number, longitude: number, time:string): Promise<UserPhoto> => {
let base64Data: string;
// "hybrid" will detect Cordova or Capacitor;
if (isPlatform('hybrid')) {
const file = await Filesystem.readFile({
path: photo.path!
});
base64Data = file.data;
} else {
base64Data = await base64FromPath(photo.webPath!);
}
console.log("base64Data")
console.log(base64Data)
let savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data
});
console.log(savedFile)
if (isPlatform('hybrid')) {
// Display the new image by rewriting the 'file://' path to HTTP
// Details: https://ionicframework.com/docs/building/webview#file-protocol
return {
filepath: savedFile.uri,
webviewPath: Capacitor.convertFileSrc(savedFile.uri),
latitude: latitude,
longitude: longitude,
time: time
};
}
else {
// Use webPath to display the new image instead of base64 since it's
// already loaded into memory
return {
filepath: fileName,
webviewPath: photo.webPath,
latitude: latitude,
longitude: longitude,
time: time
};
}
};
const deletePhoto = async (photo: UserPhoto) => {
// Remove this photo from the Photos reference data array
const newPhotos = photos.filter(p => p.filepath !== photo.filepath);
// Update photos array cache by overwriting the existing photo array
Storage.set({key: PHOTO_STORAGE, value: JSON.stringify(newPhotos) });
// delete photo file from filesystem
const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1);
await Filesystem.deleteFile({
path: filename,
directory: Directory.Data
});
setPhotos(newPhotos);
};
return {
deletePhoto,
photos,
takePhoto
};
}
export interface UserPhoto {
filepath: string;
webviewPath?: string;
latitude: number;
longitude: number;
time: string;
}
export async function base64FromPath(path: string): Promise<string> {
const response = await fetch(path);
const blob = await response.blob();
//const blob = new Blob(neu)
return new Promise((resolve, reject) => {
const reader = new FileReader();
console.log(reader)
reader.onerror = reject;
reader.onload = () => {
if (typeof reader.result === 'string') {
resolve(reader.result);
} else {
reject('method did not return a string')
}
};
reader.readAsDataURL(blob);
});
}
To access is in my UI.tsx I use eg {photo.latitude} to display the taken latitude value

waiting array of urls after uploading images to firebase storage and then stroing it again to firestore

const sendImageToFirebase = (e) => {
const promises = []
const urlsArray = []
// productimage is an array of image files
productImage.forEach((image, i) => {
var storageRef = firebase.storage().ref();
var uploadTask = storageRef.child(`${userDetailsFirebase.uid}/` + Math.random()).put(image);
promises.push(uploadTask.on('state_changed',
(snapshot) => {
},
(error) => {
console.log("error");
},
async () => {
const downloadurl = await uploadTask.snapshot.ref.getDownloadURL()
urlsArray.push(downloadurl)
}
))
})
Promise.all(promises).then(res => {
db.collection("products").doc(idGeneratedforProduct).set(
{
imageURL: urlsArray, //array of image urls
},
).then(e => {
}).catch(error => console.log("Error while sendig items to Firebase"))
})
}
I want to upload a multiple images to firebase storage. Here, sendImagToFirebase is a normal function in reactJs, and productimage is an array of image files. I want to wait for URL for each image files and then store all of them as an array to firestore. I would appreciate your input on how to do it?
You can create a function that receoves the ref and the file and returns the downloadURL. By calling it for each file with a Promise.all you get as result your array of downloadURLs:
const uploadFileAndGetDownloadURL = async (ref, file) => {
const snap = await ref.put(file);
const downloadURL = await snap.ref.getDownloadURL();
return downloadURL;
};
const sendImageToFirebase = async (e) => {
const promises = [];
productImage.forEach((image, i) => {
var storageRef = firebase.storage().ref();
var ref = storageRef.child(`${userDetailsFirebase.uid}/` + Math.random());
promises.push(uploadFileAndGetDownloadURL(ref, image));
});
//Your array with the urls
const urlsArray = await Promise.all(promises);
};

How Can I save a blob file with a form submission using React.js + Django Rest Framework

I am trying to submit a cropped an image generated in a react app using react-image-crop,and save it to a Django Rest Api using Axios.
The app uses React, Redux and Axios on the frontend and Django Rest Framework on the backend.
The form was submitting fine without the file and saving in django without the code for the file added.
Now that the file is added to the form submission the server returns a 400 error.
I suspect that I am not submitting the blob in the correct format to the django server, but I am unsure on how to proceed.
Update: I have used axios below to convert the blob url to a blob and now I am trying to a file that I can submit to a django rest api. The form submits to the django rest API without the file, but when the file is add into the form submission, I receive a 400 error. I have Updated the code to reflect my latest integrations. I have included the code where I set the headers to multipart/form-data. The error seems to be in the file conversion process in the onSubmit() method below.
Here is my relevant code:
Importing the react-image-crop library.
// Cropper
import 'react-image-crop/dist/ReactCrop.css';
import ReactCrop from 'react-image-crop';
Function inside of a react hook:
const AdCreator = ({ addFBFeedAd }) => {
const [title, setTitle] = useState('');
const [headline, setHeadline] = useState('');
const [ad_text, setAdText] = useState('');
const cropper = useRef();
// Cropper
const [upImg, setUpImg] = useState();
const imgRef = useRef(null);
const [crop, setCrop] = useState({ unit: '%', width: 30, aspect: 1.91 / 1 });
const [previewUrl, setPreviewUrl] = useState();
const onSelectFile = e => {
if (e.target.files && e.target.files.length > 0) {
const reader = new FileReader();
reader.addEventListener('load', () => setUpImg(reader.result));
reader.readAsDataURL(e.target.files[0]);
}
};
const onLoad = useCallback(img => {
imgRef.current = img;
}, []);
const makeClientCrop = async crop => {
if (imgRef.current && crop.width && crop.height) {
createCropPreview(imgRef.current, crop, 'newFile.jpeg');
}
};
const makePostCrop = async crop => {
if (imgRef.current && crop.width && crop.height) {
createCropPreview(imgRef.current, crop, 'newFile.jpeg');
}
};
const createCropPreview = async (image, crop, fileName) => {
const canvas = document.createElement('canvas');
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
canvas.width = crop.width;
canvas.height = crop.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width,
crop.height
);
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
if (!blob) {
reject(new Error('Canvas is empty'));
return;
}
blob.name = fileName;
window.URL.revokeObjectURL(previewUrl);
setPreviewUrl(window.URL.createObjectURL(blob));
}, 'image/jpeg');
});
};
const onSubmit = (e) => {
e.preventDefault();
const config = { responseType: 'blob' };
let file = axios.get(previewUrl, config).then(response => {
new File([response.data], title, {type:"image/jpg", lastModified:new Date()});
});
let formData = new FormData();
formData.append('title', title);
formData.append('headline', headline);
formData.append('ad_text', ad_text);
formData.append('file', file);
addFBFeedAd(formData);
};
return (
The Form portion:
<form method="post" id='uploadForm'>
<div className="input-field">
<label for="id_file">Upload Your Image</label>
<br/>
{/* {{form.file}} */}
</div>
<div>
<div>
<input type="file" accept="image/*" onChange={onSelectFile} />
</div>
<ReactCrop
src={upImg}
onImageLoaded={onLoad}
crop={crop}
onChange={c => setCrop(c)}
onComplete={makeClientCrop}
ref={cropper}
/>
{previewUrl && <img alt="Crop preview" src={previewUrl} />}
</div>
<button className="btn darken-2 white-text btn-large teal btn-extend" id='savePhoto' onClick={onSubmit} value="Save Ad">Save Ad</button>
</form>
Here is the Axios Call:
export const addFBFeedAd = (fbFeedAd) => (dispatch, getState) => {
setLoading();
axios
.post(`http://localhost:8000/api/fb-feed-ads/`, fbFeedAd, tokenMultiPartConfig(getState))
.then((res) => {
dispatch(createMessage({ addFBFeedAd: 'Ad Added' }));
dispatch({
type: SAVE_AD,
payload: res,
});
})
.catch((err) => dispatch(returnErrors(err)));
}
Here Is where I set the headers to multipart form data
export const tokenMultiPartConfig = (getState) => {
// Get token from state
const token = getState().auth.token;
// Headers
const config = {
headers: {
"Content-type": "multipart/form-data",
},
};
// If token, add to headers config
if (token) {
config.headers['Authorization'] = `Token ${token}`;
}
return config;
};
The Model:
class FB_Feed_Ad(models.Model):
title = models.CharField(max_length=100, blank=True)
headline = models.CharField(max_length=25, blank=True)
ad_text = models.CharField(max_length=125, blank=True)
file = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True)
The crop preview blob:
blob:http://localhost:3000/27bb58e5-4d90-481d-86ab-7baa717cc023
I console.log-ed the Cropped Image after the axios call.
File:
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: undefined
AdCreator.js:169 formData:
FormData {}
__proto__: FormData
As you can see I am trying to submit the blob image file generated by the react-image-cropper, as part of the form data when the form is submitted. I want to save the cropped image to the Django Rest API.
Any suggestions?
you should send it as "Content-Type": "multipart/form-data" to django imageField. So you should convert your blob file appropriately:
let cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
let arr = this.cropImg.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let imageCrop = new File([u8arr], 'imagename', { type: mime });
const fd = new FormData();
fd.append("avatar", imageCrop);
// send fd to axios post method.
// You should pass in post request "Content-Type": "multipart/form-data" inside headers.

React-Dropzone how convert each file to base64

I am using react-dropzone plugin for file uploads. I worried how i can convert each file to base64:
eg:
Here is my function where i get files:
I am creating here for example thumb for each file and attach to object. But how add to item here prop like base64string: and it will keep base64 data for each file?
this.onDrop = files => {
files.map(file =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
);
};
check this out,you can get files and then you can store them into images array of state instance.
onDropHandler = (files) => {
files.map(file => {
const reader = new FileReader();
reader.onload = (event) => {
//store result into your state array.
this.setState(prevState => {
const updatedImages = [...prevState.images, event.target.result];
return {
images: updatedImages,
}
})
console.log(event.target.result);
};
reader.readAsDataURL(file);
});
}
With this code you get an array with the base64 data and blob url for the preview.
const [data, setData] = useState([])
const onDrop = (files) => {
files.map((file) => {
const reader = new FileReader()
reader.onload = (e) => {
setdata(
files.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
base64Image: e.target.result,
})
)
)
}
reader.readAsDataURL(file)
})
}

Resources