Author avatar

Raphael Alampay

Using React.js and jQuery to Update a Profile Picture with a Preview

Raphael Alampay

  • Sep 21, 2020
  • 9 Min read
  • 117 Views
  • Sep 21, 2020
  • 9 Min read
  • 117 Views
Web Development
Front End Web Development
Client-side Frameworks
React

Introduction

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.

Setup

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react';
import $ from 'jquery';

export default class PictureUploader extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      picture: false,
      src: false
    }
  }

  render() {
    return (
      <div>
        <h5>Picture Uploader</h5>

        <input
          type="file"
        />
        <br/>
        <div>
          <p>
            No Preview
          </p>
        </div>
        <hr/>
        <button>
          Upload
        </button>
      </div>
    );
  }
}
javascript

States to Maintain

Within the component's structure, there are two state values:

  • picture: A reference to the picture file selected by the user
  • src: The value of the source URL for our picture preview

Both values are initially set to false when the component is created.

User Interface

The rendered content is an interface with three major sections:

  1. An interface to allow the user to select a picture from their computer
1
<input type="file"/>
html
  1. A preview section for when the user selects the picture. For now, it will say "No Preview" within a <p> element.
1
2
3
<p>
  No Preview
</p>
html
  1. A button for triggering the upload
1
2
3
<button>
  Upload
</button>
html

Picture File Selection

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.

Event Handler Function

The event handler will extract the file picture and create a URL for the preview. Copy the following handler code to your component:

1
2
3
4
5
6
7
8
9
handlePictureSelected(event) {
  var picture = event.target.files[0];
  var src     = URL.createObjectURL(picture);

  this.setState({
    picture: picture,
    src: src
  });
}
javascript

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:

1
var picture = event.target.files[0];
javascript

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.

1
var src = URL.createObjectURL(picture);
javascript

Finally update the state of your component using this.setState().

1
2
3
4
this.setState({
  picture: picture,
  src: src
});
javascript

Binding to input

Bind the function to the file input's onChange attribute:

1
2
3
4
<input
  type="file"
  onChange={this.handlePictureSelected.bind(this)}
/>
jsx

Attaching .bind(this) will allow this to still retain its value, referring to the instance of the component even within the handlePictureSelected method.

Render Preview

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."

1
2
3
4
5
6
7
8
9
10
11
12
13
renderPreview() {
  if(this.state.src) {
    return (
      <img src={this.state.src}/>
    );
  } else {
    return (
      <p>
        No Preview
      </p>
    );
  }
}
javascript

Invoke this method within the render() method of the component, specifically as a replacement for the "No Preview" text you had initially.

1
2
3
<div>
{this.renderPreview()}
</div>
jsx

Trigger Upload

Event Handler Function

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
upload() {
  var formData = new FormData();

  formData.append("file", this.state.picture);

  $.ajax({
    url: "/some/api/endpoint",
    method: "POST",
    data: formData,
    cache: false,
    contentType: false,
    processData: false,
    success: function(response) {
      // Code to handle a succesful upload
    }
  });
}
javascript

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:

  1. "file": A string representing the parameter key to be processed by the server.
  2. this.state.picture: The picture object itself extracted from state.
1
2
var formData = new FormData();
formData.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 to Button

Bind the event handler for uploading to your button element's onClick attribute:

1
2
3
4
5
  <button
    onClick={this.upload.bind(this)}
  >
    Upload
  </button>
javascript

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.

Overall Code

The final code will look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import React from 'react';
import $ from 'jquery';

export default class PictureUploader extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      picture: false,
      src: false
    }
  }

  handlePictureSelected(event) {
    var picture = event.target.files[0];
    var src     = URL.createObjectURL(picture);

    this.setState({
      picture: picture,
      src: src
    });
  }

  renderPreview() {
    if(this.state.src) {
      return (
        <img src={this.state.src}/>
      );
    } else {
      return (
        <p>
          No Preview
        </p>
      );
    }
  }

  upload() {
    var formData = new FormData();

    formData.append("file", this.state.picture);

    $.ajax({
      url: "/some/api/endpoint",
      method: "POST",
      data: formData,
      cache: false,
      contentType: false,
      processData: false,
      success: function(response) {
        // Code to handle a succesful upload
      }
    });
  }

  render() {
    return (
      <div>
        <h5>Picture Uploader</h5>

        <input
          type="file"
          onChange={this.handlePictureSelected.bind(this)}
        />
        <br/>
        <div>
        {this.renderPreview()}
        </div>
        <hr/>
        <button
          onClick={this.upload.bind(this)}
        >
          Upload
        </button>
      </div>
    );
  }
}
javascript

Conclusion

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.

2