Asís García
Aug 5 · 2 min read
Photo by Vincent Botta on Unsplash

The problem

React documentation states that:

In React, an <input type="file" /> is always an uncontrolled component because its value can only be set by a user, and not programmatically.

That makes sense, because an HTML <input type="file" /> won’t let you set its value (see note 2 here).

But a file input element has another attribute, files, that you can use to get a list of the selected files. And you can set the files attribute via the DOM. You can create your own component and use useEffect and useRef to simulate a controlled file input component:

The problem is, the component above isn’t controlled. If you render it like this:

<FileInput value="" />

You’d expect it to always be empty. But if the user clicks the file input, whatever she selects will be used as the input value, no matter what your prop says. And there’s nothing you can do about it. Except…

The solution

Stop using the native file input. 😅

I mean, you have to use a native file input so the user can select files (well, she can also drop some files into the browser, but that’s another story), but you can wrap it inside another component, which you can then use as a controlled one.

Your component will use a hidden file input to let the user select files, and it will expose a classic controlled component API:

  • A value prop to set the list of selected files.
  • An onChange callback that will be called with the new list of files whenever that list changes.

Some things to note:

  • The component does not emit a change event like its native counterpart. It just calls onChange with the list of selected files.
  • In fact, the component does not pass a FileList to the onChange callback, because those are immutable and tied to the DOM input. It passes an array with the selected files, created from the FileList.
  • If you escape the React world and start to do some imperative black magic, you’ll notice that the DOM input might have a different value than the one the wrapper component is receiving via props. Well, that’s “intended behavior”™️. We’ve already established that you can’t control the native input.
  • While it’s not shown in the implementation above, you could add more UI elements to let the user remove specific files, or append new files to the ones previously selected.

I know, this component feels like cheating. Because we are cheating, big time 😅. But trying to bend the way the browser works leads to all kind of pain. Keep it simple and embrace the constraints of the platform: you’ll be happier.

Trabe

We are a development studio. We use Java, Rails, and JavaScript. This is where we write about the technologies we use at Trabe.

Thanks to David Barral and Martín Lamas

Asís García

Written by

Developer @Trabe

Trabe

Trabe

We are a development studio. We use Java, Rails, and JavaScript. This is where we write about the technologies we use at Trabe.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade