Written by Joseph Mawa✏️
Introduction
Images make up a significant proportion of data transmitted on the internet. More often than not, clients have to upload image files from their devices to the server. To ensure users upload image files of the correct type, quality, and size, most web applications have features for previewing images.
In the browser environment, clients can initiate image upload by browsing files using an input
element or the drag and drop API. You can then use the URL
API or the FileReader
API to read the image files and preview them.
Though previewing images with the URL
API is straightforward, using the FileReader
API can be daunting. Therefore, in this article, you will learn how to preview images in a React application with the FileReader
API. We shall cover both single and batch image previews.
Contents
- How to browse image files in React
- Introduction to the
FileReader
API - How to preview single image before upload in React with the
FileReader
API - How to preview multiple images before upload in React with the
FileReader
API - Conclusion
How to browse image files in React
If you want to add file upload functionality to your web application, an input
element of type file
comes in handy. It enables users to select single or multiple files from storage in their computer or mobile device:
<input type="file" accept="image/*" multiple />
The above input
element will look like a button when rendered by the browser. Clicking it will open the operating system's built-in file chooser dialog. The user can then select the image files for upload.
The input
element has the accept
attribute for restricting the file type. Its value is a string consisting of file type specifiers separated by commas. The value of the accept
attribute is image/*
in the input
element above. It enables us to browse and upload images of any format.
To upload image files of a specific format, you can restrict the value of the accept
attribute. For example, setting its value to image/png
or .png
only accepts PNG images.
With the multiple
boolean attribute set to true
, a user can select multiple image files. On the other hand, a user can browse only one image file if its value is false
. It is worth noting that a boolean attribute's value is true
if the attribute is present on an element, and false
if omitted.
The browser emits the change
event after a user completes the file selection. Therefore, you should listen for the change
event on the input
element. You can do it like so in React:
<form>
<p>
<label htmlFor="file">Upload images</label>
<input
type="file"
id="file"
onChange={changeHandler}
accept="image/*"
multiple
/>
</p>
</form>
In the change
event handler, you can access the FileList
object. It is an iterable whose entries are File
objects. The File
objects contain read-only metadata such as the file name, type, and size:
const changeHandler = (e) => {
const { files } = e.target
for (let i = 0; i < files.length; i++) {
const file = files[i]; // OR const file = files.item(i);
}
}
Introduction to the FileReader
API
The FileReader
API provides an interface for asynchronously reading the contents of a file from a web application.
As highlighted in the previous section, you can use an input
element of type file
to browse files from a user's computer or mobile device. Selecting image files this way returns a FileList
object whose entries are File
objects.
The FileReader
API then uses the File
object to asynchronously read the file the user has selected. It is worth mentioning that you can not use the FileReader
API to read the contents of a file from the user's file system using the file's pathname.
The FileReader
API has several asynchronous instance methods for performing read operations. These methods include:
-
readAsArrayBuffer
-
readAsBinaryString
-
readAsDataURL
-
readAsText
In this article, we shall use the readAsDataURL
method. The readAsDataURL
method takes the file object as an argument, and asynchronously reads the image file into memory as data URL.
It emits the change
event after completing the read
operation:
const fileReader = new FileReader();
fileReader.onchange = (e) => {
const { result } = e.target;
}
fileReader.readAsDataURL(fileObject);
You can read the documentation for a detailed explanation of the other FileReader
instance methods.
How to preview single image before upload in React
In this section, we shall look at how to preview a single image before uploading in React with the FileReader
API. It assumes you have a React project set up already.
The code below shows how to read and preview a single image in React with the FileReader
API. We are using an input
element of type file
to browse image files. Because we want to preview a single image, I have omitted the multiple
boolean attribute on the input
element:
import { useEffect, useState } from 'react';
const imageMimeType = /image\/(png|jpg|jpeg)/i;
function App() {
const [file, setFile] = useState(null);
const [fileDataURL, setFileDataURL] = useState(null);
const changeHandler = (e) => {
const file = e.target.files[0];
if (!file.type.match(imageMimeType)) {
alert("Image mime type is not valid");
return;
}
setFile(file);
}
useEffect(() => {
let fileReader, isCancel = false;
if (file) {
fileReader = new FileReader();
fileReader.onload = (e) => {
const { result } = e.target;
if (result && !isCancel) {
setFileDataURL(result)
}
}
fileReader.readAsDataURL(file);
}
return () => {
isCancel = true;
if (fileReader && fileReader.readyState === 1) {
fileReader.abort();
}
}
}, [file]);
return (
<>
<form>
<p>
<label htmlFor='image'> Browse images </label>
<input
type="file"
id='image'
accept='.png, .jpg, .jpeg'
onChange={changeHandler}
/>
</p>
<p>
<input type="submit" label="Upload" />
</p>
</form>
{fileDataURL ?
<p className="img-preview-wrapper">
{
<img src={fileDataURL} alt="preview" />
}
</p> : null}
</>
);
}
export default App;
As illustrated in the above example, you can listen for the change
event on the input
element. The change
event handler is invoked after a client completes the file selection. You can access the File
object representing the selected file and update state in the event handler.
Since the HTML markup in the browser is editable, it is necessary to check the MIME type of the selected file before starting the read process. Though it is unlikely that an ordinary user would edit the HTML elements on a web page, it prevents anyone from easily breaking your app.
After uploading your files, you will have to do a similar check on the server side. At this point, you can also check the size of the selected file to make sure it does not exceed a maximum limit.
Since reading the selected file is a side effect, we use the useEffect
hook. As highlighted in the previous section, you start by creating an instance of FileReader
. The readAsDataURL
method of the FileReader
API reads the file asynchronously and emits the load
event after completing the reading process.
It is possible for the component to unmount or rerender before completing the read process. You will need to abort before unmounting if the read process is incomplete. To prevent memory leaks, React disallows state updates after unmounting a component. Therefore, we need to check whether the component is still mounted before updating state in the load event handler.
We access the file's data as a base64-encoded string and update the state after completing the read process. After that, you can render the image preview. For simplicity, I have not added any styling to the form
element in the above example.
How to preview multiple images before upload in React
In this section, we shall look at how to preview multiple images before uploading in React with the FileReader
API. Like the previous section, it assumes you have a React project set up already.
Reading and previewing multiple images is similar to previewing a single image. We shall modify the code in the previous section slightly. To browse and select several image files, you need to set the value of the multiple
boolean attribute to true
on the input
element.
One noticeable difference is that we are looping through the FileList
object in the useEffect
Hook and reading the contents of all the selected files before updating the state. We are storing the data URL of each image file in an array, and updating state after reading the last file.
The code below is a modification of the previous example for previewing images in a batch:
import { useEffect, useState } from "react";
const imageTypeRegex = /image\/(png|jpg|jpeg)/gm;
function App() {
const [imageFiles, setImageFiles] = useState([]);
const [images, setImages] = useState([]);
const changeHandler = (e) => {
const { files } = e.target;
const validImageFiles = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.type.match(imageTypeRegex)) {
validImageFiles.push(file);
}
}
if (validImageFiles.length) {
setImageFiles(validImageFiles);
return;
}
alert("Selected images are not of valid type!");
};
useEffect(() => {
const images = [], fileReaders = [];
let isCancel = false;
if (imageFiles.length) {
imageFiles.forEach((file) => {
const fileReader = new FileReader();
fileReaders.push(fileReader);
fileReader.onload = (e) => {
const { result } = e.target;
if (result) {
images.push(result)
}
if (images.length === imageFiles.length && !isCancel) {
setImages(images);
}
}
fileReader.readAsDataURL(file);
})
};
return () => {
isCancel = true;
fileReaders.forEach(fileReader => {
if (fileReader.readyState === 1) {
fileReader.abort()
}
})
}
}, [imageFiles]);
return (
<div className="App">
<form>
<p>
<label htmlFor="file">Upload images</label>
<input
type="file"
id="file"
onChange={changeHandler}
accept="image/png, image/jpg, image/jpeg"
multiple
/>
</p>
</form>
{
images.length > 0 ?
<div>
{
images.map((image, idx) => {
return <p key={idx}> <img src={image} alt="" /> </p>
})
}
</div> : null
}
</div>
);
}
export default App;
We keep references to the FileReader
instances in an array for canceling any file reading process in the cleanup
function when the component re-renders or unmounts to avoid memory leaks.
When using a routing library like React Router, a user can navigate away from the current page, and the component unmounts before completing the file reading process. Therefore, it is necessary to do cleanup as highlighted above.
In the above example, we are asynchronously reading the files in a loop and updating state after. Because of the asynchronous nature of the file reading process, it is impossible to know which file we shall complete reading last. Therefore, we have to check the number of files read in the load
event handler before updating state. You can achieve the same with promises.
The code below shows a modification of the useEffect
Hook to use promises instead. It is cleaner and easier to think about than using loops like in the previous method:
useEffect(() => {
const fileReaders = [];
let isCancel = false;
if (imageFiles.length) {
const promises = imageFiles.map(file => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReaders.push(fileReader);
fileReader.onload = (e) => {
const { result } = e.target;
if (result) {
resolve(result);
}
}
fileReader.onabort = () => {
reject(new Error("File reading aborted"));
}
fileReader.onerror = () => {
reject(new Error("Failed to read file"));
}
fileReader.readAsDataURL(file);
})
});
Promise
.all(promises)
.then(images => {
if (!isCancel) {
setImages(images);
}
})
.catch(reason => {
console.log(reason);
});
};
return () => {
isCancel = true;
fileReaders.forEach(fileReader => {
if (fileReader.readyState === 1) {
fileReader.abort()
}
})
}
}, [imageFiles]);
Conclusion
Most web applications that require image upload from a client's storage device also come with features for previewing images. Among other reasons, previewing an image ensures your clients upload image files of the appropriate type, quality, and size.
You can initiate file upload from a client's device with an input
element of type file
or using the drag and drop interface. After selecting images, you can preview them using the URL
API or the FileReader
API. Though using the URL
API may be straightforward, the FileReader
API is not.
As highlighted in the article, you preview images singly or in a batch. Hopefully, this article gave you insights on image previews in React using the FileReader
API. Let me know what you think in the comments section below.
Full visibility into production React apps
Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.
Top comments (0)