How To Style A File Input In React
Going in, I thought the hardest part of building the user interface for uploading a file would be the actual functionality of handling that file.
With any other input in React, you can handle its value by controlling it, storing the value in a component’s state to be easily used by other functions or components. But inputting text is much different — and much faster — than uploading a whole file. I thought I had a long road ahead of me.
It turns out; it’s not that different. The really tricky part of adding a file input to a React application is styling the thing. For some reason, most browsers don’t provide any way to style the Choose File button on a file input. You can style the file name text, but no matter what you do, the button still looks very World Wide Web.
The solution is to build your own styled input on top of the HTML input that’s actually receiving the file.
HTML File Input
I don’t think HTML receives enough praise for how wonderfully straightforward it can be. If I was unable to use Google for some reason and had to just guess what a file input might look like in HTML, I think I might have been able to get it right.
It looks like this:
<input type='file' name='image' />
You want text? type='text'
. You want a file? type='file'
. Pretty great.
In React, we like to control inputs. Surely we can’t just control a file input like any other input?
Oh, we can:
const ImageInput = ({ file, setFile }) => {
const onChange = async e => {
if (e.target.files && e.target.files.length > 0) {
setFile(e.target.files[0])
}
} return <input type='file' name='image' onChange={onChange} />
}
Controlling a file input is almost identical to controlling other inputs with one important difference: the onChange
function is asynchronous. This makes sense because, as I expected, uploading a file takes more time than inputting text.
You might have noticed that e.target.files
is an array — I’m grabbing the first item in the array with e.target.files[0]
. A file input will automatically accept multiple files. You can override this by adding a multiple={false}
attribute to the input.
Hiding The Default File Input
Now we have the file in our state, but we’re stuck with the bland Choose File button provided by the browser. The next step is to hide the input while still using its functionality.
If I hide the input, the user will have no way to interact with the DOM node. That means that my application needs access to the DOM node so that it can click the button on the user’s behalf.
React allows us to do this with the useRef
hook, which stores a reference to a DOM node in a variable that can be used within a component. To use useRef
, import it:
import React, { useState, useRef } from 'react'
Declare a variable for the DOM node:
const fileInput = useRef(null)
And then tell the input where to store itself with a ref
attribute:
<input
type='file'
name='image'
ref={fileInput}
onChange={onChange}
style={{ display: 'none' }}
/>
Now the hidden file input DOM node will be available at fileInput.current
. From here, I can build a Choose File button of my own by connecting its onClick
function to the HTML input’s button:
<button
className='upload-btn'
onClick={() => fileInput.current.click()}
>Choose File</button>
The button serves as a sort of middleman between the user and the hidden HTML input. My button receives the click and passes it through to the real input, which tells the browser to open the local file browser and receive the selected file.