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
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…
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:
valueprop to set the list of selected files.
onChangecallback 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
onChangewith the list of selected files.
- In fact, the component does not pass a
onChangecallback, because those are immutable and tied to the DOM
input. It passes an array with the selected files, created from the
- If you escape the React world and start to do some imperative black magic, you’ll notice that the DOM
inputmight 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.