Displaying a preview of an image upload in React
Show your user a preview of the photo they want to upload
The button below should look familiar. We use it all the time to upload photos, add attachments to emails, submit homework, etc.
I decided to make a React component that lets you choose an image file, and display the preview of it. My plan is to do user authentication next, and then add the upload functionality to let people store and access their images in a database. But this article will be focusing only on the simple image preview. Most tutorials tell you to use fileReader()
but it looked too complicated for such a simple functionality. Instead I found a really simple way to do it. Here is the whole code:
const React = require('react')class Upload extends React.Component {
constructor(props){
super(props)
this.state = {
file: null
} this.handleChange = this.handleChange.bind(this)
} handleChange(event) {
this.setState({
file: URL.createObjectURL(event.target.files[0])
})
} render() {
return (
<div>
<input type="file" onChange={this.handleChange}/>
<img src={this.state.file}/>
</div>
);
}
}
module.exports = Upload
This is a very typical React component, with the only strange line being the one in bold. What’s going on there? Lets investigate.
According to the File Web API documentation, the html element <input/ type="file">
allows the user to select a local file and add it to the FileList
object which stores the selected file to a files
array, similarly to how React stores properties to this.state
. To access the file we would do something like this:
//hmtl
<input id="fileItem" type="file">//js
var file = document.getElementById('fileItem').files[0];
Handling events
In React, we don’t use document.getElementById()
or anything like that to access the DOM. Instead we use component’s state to store and access relevant data. In this case use the onChange
event, which normally would look like this in HTML:
<input onchange="handleChange(event)">
In JSX:
<input onChange={ (event) => this.handleChange(event) }>
However, we want to avoid this way of writing JSX because it causes the creation of a new callback function every time the the input re-renders. This is usually not a problem, but it can have performance implications if the callback is passed to a lower level component.
The common approach in React is to explicitly bind the function in the component’s constructor like this:
this.handleChange = this.handleChange.bind(this)
Now you can reference it properly in your JSX like this:
<input onChange={ this.handleChange }>
But what is our function doing? Let’s get back to that strange line we use to store our file.
Every time there is a onChange
event, our function is called with that event passed in as an argument. The event has some properties we can access with event.propertyName
. The property we want is called target
and it gives us the DOM element where the function was called from, which is our input element.
Now remember that our input can store a file to FileList
file array, which we can access with the with the files
property. here’s what it looks like:
event.target.files[0] //our file
objectURL ?
The URL.creatObjectURL()
method takes an object (like our file) and creates a temporary local URL that is tied to the document
in which it is created (meaning it won’t remember the URL if you leave the page and come back). This URL can be used to set the the src
property of a <img/>
element. But before we do that, we want to set this URL in the component’s state using setState()
because it causes the state changes to be re-rendered to the DOM. That way as soon as a new image file is selected, it gets displayed immediately.
Now that we store the image URL every time it changes, we always have access to it with this.state.file
.
That’s it! Next time we will add authentication using Firebase, and then make the upload functionality so users can store and access their photos.