DEV Community

Cover image for How to Upload Files in 2022?
Ezell Frazier
Ezell Frazier

Posted on

How to Upload Files in 2022?

Can we finally do away with the Input Element and FileReader API?

Disclaimer: This post involves the context of handling and reading files within a JavaScript-based web application. There's nothing wrong with using an input element for file uploads for other purposes. However, it's wonderful to have an alternative approach.

The Old Way of Uploading Files with JavaScript

So, I'm writing an application and I want to allow for users to save and load their configurations. JSON is likely the simplest approach here. Or, I'd think.

<input type="file" accept=".json" id="upload"/>
Enter fullscreen mode Exit fullscreen mode
const uploadElement = document.getElementById('upload')

const reader = new FileReader();

// Two event handlers to for uploading and reading files
reader.onload = () => {
  const json = reader.result
  const data = JSON.parse(json)
  console.log(data)
}

uploadElement.onchange = (e) => {
  const [file] = e.target.files
  reader.readAsText(file)
}
Enter fullscreen mode Exit fullscreen mode

The old approach involves declaring two input handlers for two asynchronous operations. The reader object can read uploaded files, however reader.result is not available in a traditional sequence, as seen below:

const uploadElement = document.getElementById('upload')

const reader = new FileReader();

uploadElement.onchange = (e) => {
  const [file] = e.target.files
  reader.readAsText(file)
  console.log(reader.result) // why is this null?
}
Enter fullscreen mode Exit fullscreen mode

reader.result is null because it hasn't finished reading the file. So, a workaround involves using the reader's readAsText method in tandem with a Promise constructor.

const uploadElement = document.getElementById("upload");

const reader = new FileReader();

// Poll for changes to the filereader's state about every 25ms
const readTextAsync = (filereader, fileBlob) =>
  new Promise((resolve) => {
    let interval;
    filereader.readAsText(file);

    interval = setInterval(() => {
      if (filereader.readyState === FileReader.DONE) {
        clearInterval(interval);
        const result = filereader.result;
        resolve(result);
      }
    }, 25);

  });

uploadElement.onchange = async (e) => {
  const [file] = e.target.files;
  const json = await readTextAsync(reader, file)
  console.log(json) // We have our json string
};
Enter fullscreen mode Exit fullscreen mode

There's no longer two event handlers, but there's more code.

Converting callback-based APIs into something more modern may be more tedious to write, harder to read, and possibly introduce more opportunities for bugs. Thankfully, we can use the recently introduced File System Access API to tackle this problem.

Uploading files with the File System Access API

<button id="upload">Select a File</button>
Enter fullscreen mode Exit fullscreen mode
const uploadBtn = document.getElementById("upload");

// I can clearly see and understand what's happening here
uploadBtn.onclick = async () => {
  const options = {
    types: [
      {
        description: "JSON",
        accept: {
          "application/json": [".json"],
        },
      },
    ],
  };
  const [fileHandle] = await window.showOpenFilePicker(options);
  const file = await fileHandle.getFile();
  const json = await file.text();
  console.log(json);
};
Enter fullscreen mode Exit fullscreen mode

There's many improvements to the developer experience here. window.showOpenFilePicker allows us to place the following all in one callback function:

  • File reading
  • File parsing
  • File Picker Options

However, the biggest benefit is not having tightly related implementation details within multiple places across multiple files, across multiple steps.

Lastly, the input element may introduce a design which clashes with the overall theme of the UI. It's not uncommon to see implementations of a button element for file uploading, while hiding the actual input element responsible for the file upload.

None of that is required here.

Caveats

  • The File System Access API is fully supported within Chrome and Edge browsers as of this post. However, Polyfills are available
  • The FileSystem API only works in secure, https:// environments, with the exception of localhost
  • The FileSystem API also won't work within iframes or other framed environments like CodePen / CodeSandbox, etc.

Learn more about the File System Access API on MDN

Discussion (0)