How do I convert image file to base64? - reactjs

I am want to save image as base64 to aws s3 bucket. There is a lambda that will decoder the base64.
These are my current states for the images. One is for selected file that is the image and the one is for the image that is seen as preview on the page.
const [selectedFile, setSelectedFile] = useState('')
const [preview, setPreview] = useState()
Then I have useEffect function for selecting the file and also sets the object URL as the preview image.
useEffect(() => {
if (!selectedFile) {
setPreview(undefined)
return
}
const objectURL = window.URL.createObjectURL(selectedFile)
setPreview(objectURL)
return () => window.URL.revokeObjectURL(objectURL)
}, [selectedFile])
const selectFile = (event) => {
setSelectedFile(event.target.files[0])
}
And this is the input component where the onChange function is called.
<Input
style={input}
type='file'
accept='.jpg, .png|image/*'
id='image'
name='Upload image'
onChange={selectFile}
/>
Is there a better way to handle the base64 conversion?

I managed to solve this by relatively short lines of code. I take the selectedFile from the state and then convert it to base64. I tested it with the separate button and I got base64 image in the console.
const convertToBase64 = () => {
const reader = new FileReader()
reader.readAsDataURL(selectedFile)
reader.onload = () => {
console.log('called: ', reader)
setBase64IMG(reader.result)
}
}

Related

How to use PDF.JS with React?

I would like to parse a pdf file in a React app. The pdf will be provided through a html input.
I used pdf-parse - a wrapper around pdf.js in node - without any problem. But when it comes to React, I only receive this error:
MissingPDFException {message: 'Missing PDF "http://localhost:3000/myfile.pdf".', name: 'MissingPDFException'}
I upload the file like this:
export default function Home() {
const [data, setData] = useState();
const handleFile = (e) => {
const file = e.target.files[0];
const fileReader = new FileReader();
fileReader.onload = (d) => {
setData(new Uint32Array(d.target.result));
};
};
return (
<>
<h1>hello!</h1>
<input
type="file"
accept="application/pdf"
placeholder="insert PDF here"
onChange={(e) => handleFile(e)}
/>
<PDFViewer pdfFile={data} />
</>
);
}
And The file is supposed to be read here:
import * as PDFJS from "pdfjs-dist/build/pdf";
import * as pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
window.PDFJS = PDFJS;
export default function PDFViewer({ pdfFile }) {
PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const getPDFDoc = useCallback(async () => {
const doc = await PDFJS.getDocument(pdfFile);
doc.promise.then(
(loadedPdf) => {
setPdfRef(loadedPdf);
},
function (reason) {
console.error(reason);
}
);
}, []);
useEffect(() => {
getPDFDoc();
}, [getPDFDoc]);
I doesn't seem to work at all. I have a custom config with webpack, typescript and SWC-loader. I have read all the related stackoverflow threads.
How to properly parse a PDF with PDF.js in React? If there is a better library, I'm open to any suggestions. My goal is not to display the pdf, but to get its content.
Your component only runs getPDFDoc on mount since pdfFile is missing in the usecallback deps, so when the file changes, it probably doesn't even notice as your effect won't re-run since getPDFDoc is referentially stable when it shouldn't be.
Try
import * as PDFJS from "pdfjs-dist/build/pdf";
import * as pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
window.PDFJS = PDFJS;
export default function PDFViewer({ pdfFile }) {
PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const getPDFDoc = useCallback(async () => {
if (!pdfFile) return
const doc = await PDFJS.getDocument(pdfFile);
doc.promise.then(
(loadedPdf) => {
setPdfRef(loadedPdf);
},
function (reason) {
console.error(reason);
}
);
}, [pdfFile]);
useEffect(() => {
getPDFDoc();
}, [getPDFDoc]);
I think the reason for the weird "myfile.pdf" thing is probably because when it first runs pdfFile is not defined and this might be some internal library default. So I also added a guard to not do anything when it's not set.

Argument of type 'string' is not assignable to parameter of type 'Blob'

So I am having this problem.
import React, { useState } from "react";
const ImageInput: React.FC = () => {
const [image, setImage] = useState("");
let reader = new FileReader();
reader.readAsDataURL(image);
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const imageValue = e.target.value;
setImage(imageValue);
};
return (
<input onChange={handleUpload} type="file" multiple={false} value={image} />
);
};
export default ImageInput;
I am using React with TypeScript and I'm trying to make an image upload component but it is giving me this error. I've tried researching "Blobs" but no joy. I am getting the error in the readAsDataURL.
The reader will be defined and executed on each render of the component. This seems off, it should rather happen inside a function.
As input for the readAsDataUrl one should not use a string, as TypeScript already complained, given the documentation, the input is a File or a Blob. Which can be retrieved directly from the input component. (Source: MDN)
For reference: "[..] a blob is a representation of immutable data, which can be read as text or binary" (Source: MDN)
Based on the above two links your component could look like this (note: image is currently unused):
import React, { useState, createRef } from "react";
const ImageInput: React.FC = () => {
// create a ref to keep a reference to a DOM element, in this case the input
const imagesUpload = createRef<HTMLInputElement>();
const [image, setImage] = useState<string | ArrayBuffer | null>(null);
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
// get the file which was uploaded
const file = imagesUpload?.current?.files?.[0];
if (file) {
const reader = new FileReader();
reader.addEventListener("load", function () {
// convert image file to base64 string
setImage(reader.result)
}, false);
reader.readAsDataURL(file);
}
};
return (
<input ref={imagesUpload} onChange={handleUpload} type="file" multiple={false} />
);
};
export default ImageInput;

Updating state while in a JSZIP file

I'm using the library JSZip to read a user-uploaded zip file and I want to load the contents of each file into the state.
Here's a toned-down version of reading the zip file:
const [files, setFiles] = useState([]);
const onUploadClick = () => {
const load = (filename) => {
return new Promise(
(resolve) => {
jzip.file(filename).async(`arraybuffer`).then(
(content) => {
return resolve(Array.from(new Uint8Array(content)));
}
)
}
)
}
jzip.loadAsync(file).then(
(zip) => {
zip.forEach(
async (_, entry) => {
const filename = entry[`name`];
const buffer = await load(filename);
const newFiles = [...files];
newFiles.push(buffer);
setFiles([...newFiles]);
)
}
)
}
I then used useEffect to monitor the changes to the files state component:
useEffect(
() => {
console.log(files)
},
[files]
);
But what I get out is just the following when trying to load a zip file with 3 files inside:
Instead, I am expecting something like
[Array(33), Array(33), Array(33)]
So it's reading each file correctly, converting to a Uint8Array, and adding it to the state, but the state is not retaining the previous files.
I also tried pushing just the filename into the files state component, and same error.
I am unsure why this is happening. Any advice is appreciated. Thank you!
Fixed code below:
const [files, setFiles] = useState([]);
const onUploadClick = () => {
const load = (filename) => {
return new Promise(
(resolve) => {
jzip.file(filename).async(`arraybuffer`).then(
(content) => {
return resolve(Array.from(new Uint8Array(content)));
}
)
}
)
}
jzip.loadAsync(file).then(
(zip) => {
const newFiles = [];
zip.forEach(
async (_, entry) => {
const filename = entry[`name`];
const buffer = await load(filename);
newFiles.push(buffer);
}
)
setFiles(newFiles);
)
}
The state update setFiles was not finished yet before trying to add a new entry, therefore files was not updated yet when trying to add a 2nd one. Same for the third file: it pulled the current files array (which was still empty, because the state update before it had not finished) and pushed something new. That's why there was only ever one item in the list.

Photo Upload Validation with React

I have this small issue
I am using Ant Design and its components
https://ant.design/components/upload/
I am trying to upload images, and it all works fine, But i need to add a validation to verify if the file is correct. That is if a user renames any other file to a .jpg or .png extension, i need to validate that and show them an alert.
How can i achieve that, Currently we convert the image in base64 using FileReader
You can create a function and return something like this.
const verifyImage = (img) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.addEventListener('load', event => {
let picFile = event.target
let imgNew = new Image()
imgNew.addEventListener('load', () => {
resolve(reader.result)
})
imgNew.addEventListener('error', () => {
reject()
})
imgNew.src = picFile.result
})
reader.readAsDataURL(img)
})
}

Intermediate data from MediaRecorder getting lost when using React hooks

I'm working on a higher order component that will provide the ability to capture media using the MediaRecorder API. However, when I try to use the captured video (in the form of a Blob passed to createObjectURL) I am getting an error ERR_REQUEST_RANGE_NOT_SATISFIABLE. When I console log the Blob that is passed to the wrapped component, it has a length of 0. I have included my code at the end of this post.
In order to diagnose the problem, I tried the following tests:
Console logging newChunks in handleDataAvailable logs the correct value (i.e. [Blob]).
I added React.useEffect(() => console.log(chunks), [chunks]); in order to see if chunks is actually getting updated. This also results in the correct value being logged (i.e. [Blob]).
I added React.useEffect(() => console.log(captured), [captured]); in order to see if captured is getting updated. This results in a Blob of size 0 being logged.
In handleStop, I console log chunks and the blob created by combining the chunks. That results in an empty array and a blob with size 0 respectively.
This leads me to believe that handleDataAvailable is correctly adding each chunk to the chunks array, but somehow the array is being emptied by the time that handleStop gets run.
Does anyone see what might be causing that to happen?
Code:
import React from 'react';
import { getUserMedia, getConstraints } from '../../utils/general';
const withMediaCapture = (WrappedComponent, recordingType, facingMode, deviceID) => {
const constraints = getConstraints(recordingType, facingMode, deviceID);
const type = recordingType === 'audio'
? 'audio/ogg; codecs=opus'
: 'video/webm; codecs=vp9';
return props => {
const [mediaStream, setMediaStream] = React.useState(undefined);
const [mediaRecorder, setMediaRecorder] = React.useState(undefined);
const [isRecording, setIsRecording] = React.useState(false);
const [captured, setCaptured] = React.useState(undefined);
const [chunks, setChunks] = React.useState([]);
// On mount, get the mediaStream:
const setupStream = () => {
getUserMedia(constraints)
.then(setMediaStream)
.catch(error => {/* TODO: Handle error */});
};
React.useEffect(setupStream, []);
// Once we have gotten the mediaStream, get the mediaRecorder:
const handleDataAvailable = ({ data }) => {
const newChunks = [...chunks, data];
setChunks(newChunks);
};
const handleStop = foo => {
const blob = new Blob(chunks, { type });
setCaptured(blob);
setChunks([]);
}
const getMediaRecorder = () => {
mediaStream && setMediaRecorder(Object.assign(
new MediaRecorder(mediaStream),
{
ondataavailable: handleDataAvailable,
onstop: handleStop,
},
));
}
React.useEffect(getMediaRecorder, [mediaStream]);
const toggleRecording = () => {
isRecording
? mediaRecorder.stop()
: mediaRecorder.start();
setIsRecording(!isRecording);
};
return <WrappedComponent {...{ preview: mediaStream, captured, toggleRecording, isRecording, ...props }} />;
};
};
const VideoCaptureDemo = ({ preview, captured, toggleRecording, isRecording }) => {
const previewRef = React.useRef(null);
const capturedRef = React.useRef(null);
const setupPreview = () => {
previewRef.current.srcObject = preview;
};
React.useEffect(setupPreview, [preview]);
const setupCaptured = () => {
const url = captured && window.URL.createObjectURL(captured);
capturedRef.current.src = url;
};
React.useEffect(setupCaptured, [captured]);
return (
<div>
<video ref={previewRef} autoPlay={true} muted={true} />
<video ref={capturedRef} controls />
<button onClick={toggleRecording}>
{isRecording ? 'Stop Recording' : 'Start Recording'}
</button>
</div>
);
};
export default withMediaCapture(VideoCaptureDemo, 'videoAndAudio');
handleStop and handleDataAvailable are both closing over the initial, empty chunks array. If handleDataAvailable is called more than once, earlier chunks will be lost, and handleStop will always create a Blob from the empty chunks array. Re-renders caused by setChunks will cause new versions of the handle methods to be created, but the MediaRecorder will still be using the versions from when the MediaRecorder was created.
You could fix handleDataAvailable by using functional updates, but in order to fix handleStop I think you would be best off to switch to using useReducer (with the reducer managing both chunks and captured) so that you can just dispatch an action and then the reducer can have access to the current chunks and create the Blob appropriately.

Resources