Using the FileReader API to preview images in React
Tags: react
Published: 2022-04-07 16:07:09

**Written by [Joseph Mawa](https://blog.logrocket.com/author/josephmawa/)✏️**

## 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](https://blog.logrocket.com/html-5-drag-and-drop-api-a-tutorial/). 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:

```html
<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:

```js
<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:

```js
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](https://developer.mozilla.org/en-US/docs/Web/API/FileReader) 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:

```js
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:

```js
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:

```js
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](https://blog.logrocket.com/migrating-react-router-v6-complete-guide/), 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:

```js
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](https://www2.logrocket.com/react-performance-monitoring). 

![LogRocket signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) 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](https://www2.logrocket.com/react-performance-monitoring).