Developing an interface for a web app to allow users to upload a picture often means supplying an input element of type file
as well as a section to preview the picture that the user selected from their computer. This guide will go over how to implement a simple React.js component that allows a user to select a picture, preview the picture, and handle the picture itself for submitting via an AJAX call.
First, create a component that maintains two states: one for the picture itself and another for the source URL for the preview. Next, you'll need the component to render an interface that contains an input element for file selection, a section for the picture preview, and a button to trigger the upload functionality. The PictureUploader
component will initially look like the following:
1import React from 'react';
2import $ from 'jquery';
3
4export default class PictureUploader extends React.Component {
5 constructor(props) {
6 super(props);
7
8 this.state = {
9 picture: false,
10 src: false
11 }
12 }
13
14 render() {
15 return (
16 <div>
17 <h5>Picture Uploader</h5>
18
19 <input
20 type="file"
21 />
22 <br/>
23 <div>
24 <p>
25 No Preview
26 </p>
27 </div>
28 <hr/>
29 <button>
30 Upload
31 </button>
32 </div>
33 );
34 }
35}
Within the component's structure, there are two state values:
picture
: A reference to the picture file selected by the usersrc
: The value of the source URL for our picture previewBoth values are initially set to false
when the component is created.
The rendered content is an interface with three major sections:
1<input type="file"/>
<p>
element.1<p>
2 No Preview
3</p>
1<button>
2 Upload
3</button>
Next, program an event handler that will process the picture whenever the user selects a picture to upload. Afterwards, create another function to deal with the preview of the picture. If no picture is selected, return "No Preview" text. Otherwise, return an img
HTML tag containing the URL of the selected picture.
The event handler will extract the file picture and create a URL for the preview. Copy the following handler code to your component:
1handlePictureSelected(event) {
2 var picture = event.target.files[0];
3 var src = URL.createObjectURL(picture);
4
5 this.setState({
6 picture: picture,
7 src: src
8 });
9}
This method accepts an event
argument whose property target
represents the input element this handler is bound to. In this case, your input
element of type file
is expected to have a property files
, which is of data type array. Therefore, to get the file that the user selected, reference the first element of the array:
1var picture = event.target.files[0];
The src
variable represents the URL for the picture preview. You can create this by calling URL.createObjectURL()
, which accepts a file object. This creates a temporary URL attached to the global document
variable. This means that if the page is refreshed, you'll lose that URL reference. In this case, the file object is picture
.
1var src = URL.createObjectURL(picture);
Finally update the state of your component using this.setState()
.
1this.setState({
2 picture: picture,
3 src: src
4});
input
Bind the function to the file input's onChange
attribute:
1<input
2 type="file"
3 onChange={this.handlePictureSelected.bind(this)}
4/>
Attaching .bind(this)
will allow this
to still retain its value, referring to the instance of the component even within the handlePictureSelected
method.
Our preview section is a result of another function that contains an if
/else
statement. It checks the component's state src
if it is not false, indicating that you have a value. If so, return JSX code that contains an img
whose source is src
. Otherwise, return JSX code that contains the text "No Preview."
1renderPreview() {
2 if(this.state.src) {
3 return (
4 <img src={this.state.src}/>
5 );
6 } else {
7 return (
8 <p>
9 No Preview
10 </p>
11 );
12 }
13}
Invoke this method within the render()
method of the component, specifically as a replacement for the "No Preview" text you had initially.
1<div>
2{this.renderPreview()}
3</div>
Create a method called the upload
method that contains an ajax()
call from jQuery
to perform the upload. Copy the following code to your component:
1upload() {
2 var formData = new FormData();
3
4 formData.append("file", this.state.picture);
5
6 $.ajax({
7 url: "/some/api/endpoint",
8 method: "POST",
9 data: formData,
10 cache: false,
11 contentType: false,
12 processData: false,
13 success: function(response) {
14 // Code to handle a succesful upload
15 }
16 });
17}
Notice that instead of passing a plain old JavaScript object, you instead create an instance of FormData
to allow you to embed the picture object. Do so by invoking append
from formData
, which accepts two arguments:
"file"
: A string representing the parameter key to be processed by the server.this.state.picture
: The picture object itself extracted from state
.1var formData = new FormData();
2formData.append("file", this.state.picture);
Since you're passing FormData
as data
in your AJAX POST
call, you'll have to set cache
, contentType
, and processData
to false. For the url
, substitute "/some/api/endpoint"
with the actual endpoint where you would upload your picture.
Bind the event handler for uploading to your button element's onClick
attribute:
1 <button
2 onClick={this.upload.bind(this)}
3 >
4 Upload
5 </button>
Similar to your input
binding, attaching .bind(this)
will allow this
to still refer to the instance of the component even within the upload
handler.
The final code will look like this:
1import React from 'react';
2import $ from 'jquery';
3
4export default class PictureUploader extends React.Component {
5 constructor(props) {
6 super(props);
7
8 this.state = {
9 picture: false,
10 src: false
11 }
12 }
13
14 handlePictureSelected(event) {
15 var picture = event.target.files[0];
16 var src = URL.createObjectURL(picture);
17
18 this.setState({
19 picture: picture,
20 src: src
21 });
22 }
23
24 renderPreview() {
25 if(this.state.src) {
26 return (
27 <img src={this.state.src}/>
28 );
29 } else {
30 return (
31 <p>
32 No Preview
33 </p>
34 );
35 }
36 }
37
38 upload() {
39 var formData = new FormData();
40
41 formData.append("file", this.state.picture);
42
43 $.ajax({
44 url: "/some/api/endpoint",
45 method: "POST",
46 data: formData,
47 cache: false,
48 contentType: false,
49 processData: false,
50 success: function(response) {
51 // Code to handle a succesful upload
52 }
53 });
54 }
55
56 render() {
57 return (
58 <div>
59 <h5>Picture Uploader</h5>
60
61 <input
62 type="file"
63 onChange={this.handlePictureSelected.bind(this)}
64 />
65 <br/>
66 <div>
67 {this.renderPreview()}
68 </div>
69 <hr/>
70 <button
71 onClick={this.upload.bind(this)}
72 >
73 Upload
74 </button>
75 </div>
76 );
77 }
78}
The process to create a component that can process picture files uploaded by the user from their computer is the same as any other React.js-based event handling. Bind a function to the input element and extract the target's value. In this case, your value is a picture file which you can maintain with the component's state for later integration with the actual upload logic. As a challenge and next step, try to modify the upload
function code and see if you can integrate it with your app's picture upload API or a third party API endpoint that accepts picture uploads such as imgur.