How to start upload file with React and Nodejs

How to start upload file with React and Nodejs

  • 285

How to start upload file with React and Nodejs. setting up the Node server Go to server directory, open terminal and execute npm install and node index , this will run the server. Then continue from the React section.

we gonna create a basic form for handle file upload with React and server-side with Nodejs this tutorial is so simple you can follow along with 5 minutes it should be done if you think TLDR; just check finish code here and live demo will host on Codesandbox

react-file-upload

Setting up the Node server

If you’re following for the full tutorial, the complete server code is available. Go to server directory, open terminal and execute npm install and node index , this will run the server. Then continue from the React section.

  1. Create a directory upload-server, open terminal in this directory and run npm init. Accept all defaults.
  2. In the same terminal, install upload and cors package will commandnpm express express-fileupload cors

start on index.js is the entry file.

Here’s the Initial code for the server.

const PORT = 5000;
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.get('/', (req, res) => {
  return res.status(200).send("It's working");
});
app.listen(PORT, () => {
  console.log('Server Running sucessfully.');
});

Now, go to your browser at localhost:5000 and you should see It’s working.

Upload Endpoint

Let’s define /upload at the upload endpoint. It needs the middleware fileUpload, set this thing first.

app.use(
  fileUpload({
    useTempFiles: true,
    safeFileNames: true,
    preserveExtension: true,
    tempFileDir: `${__dirname}/public/files/temp`
  })
);

It accepts many options, which you can find here.

The temporary directory is used to store files temporarily instead of using computer memory(RAM). This is very useful in uploading large files.

Safe File removes non-alphanumeric characters from except dash and underscores from the filename. You can also define regex instead of true for custom names.

preserveExtension preserves the extension of the file name, this is false by default. It also supports customizations, refer to the documentation for options.

app.post('/upload', (req, res, next) => {
  let uploadFile = req.files.file;
  const name = uploadFile.name;
  const md5 = uploadFile.md5();
  const saveAs = `${md5}_${name}`;
  uploadFile.mv(`${__dirname}/public/files/${saveAs}`, function(err) {
    if (err) {
      return res.status(500).send(err);
    }
    return res.status(200).json({ status: 'uploaded', name, saveAs });
  });
});

The file in req.files.file refers to file as a name in the input field. That is NAME in both <input name="**NAME**" type="file" /> and req.file.**NAME** should be the same.

To prevent overwriting the files, I used an md5 function from the uploadFile package and saved the file as md5_filename.ext.

You can test the API using Postman as well.

This is image title

Send a post request to /upload with a file. It should successfully go through.

React Front End

A server is waiting for files, you need an interface to allow users to input the files. Let’s create an interface using React.

Set up the react application with create-react-app.

npx create-react-app upload-front

Put a form with input type file in App.js. It should look like the following:

import React, { Component } from 'react';
class App extends Component {
  render() {
    return (
      <form className="App">
        <input type="file" name="file" />
        <button>Upload</button>
      </form>
    );
  }
}
export default App;

The button won’t upload by itself. Define a few methods and states to achieve functionality.

State: selectedFile

Methods: handleFileChange, and handleUpload

Add on change listener as handleFileChange on input. You can attach handleFileUpload as a form submit an action or as click listener on a button. We’ll use the state to hold file so it doesn’t matter.

Here’s how it looks.

import React, { Component } from 'react';
class App extends Component {
  handleFileChange = () => {
    // define file change
  };
  handleUpload = () => {
    // define upload
  };
  render() {
    return (
      <form className="App" onSubmit={handleUpload}>
        <input type="file" name="file" onChange={handleFileChange} />
        <button onClick={handleUpload}>Upload</button>
      </form>
    );
  }
}
export default App;

**Do not keep handleUpload on both onClick and onSubmit.**One is enough.

Handling the file change, we keep track of file and an upload button click.

Handling file change

handleFileChange = (event) => {
    this.setState({
      selectedFile: event.target.files[0]
    });
  };

The file input is an event for the click and files are stored in an array (even in single file upload).

Handling the Upload

To handle the uploading, we’ll use Axios to send ajax request. Install and import Axios.

npm i axios

import axios from 'axios

handleUpload = (event) => {
    event.preventDefault();
    const data = new FormData();
    data.append('file', this.state.selectedFile, this.state.selectedFile.name);
    axios.post(endpoint, data).then((res) => {
      console.log(res.statusText);
    });
  };

A file is sent as FormData. It just logs the response text after completing the AJAX request.

While it looks too ugly, uploading should start working now.

Loading State

While the file is uploaded successfully, the user doesn’t see the response. Showing the response after the completion is quite simple. Let’s show the progress instead.

axios can take some parameters with the post method. One of them is ProgressEvent.

{
        onUploadProgress: ProgressEvent => {
          this.setState({
            loaded: (ProgressEvent.loaded / ProgressEvent.total*100),
          }

This sets a loaded state to whatever percentage is completed.

We also need to display it, so wrap it in a paragraph outside the form. This will crash the application as JSX can’t return multiple parts. Wrap the return in React Fragment.

Here’s the code:

return (
      <React.Fragment>
        <form className="App" onSubmit={this.handleUpload}>
          <input
            className="inputfile"
            id="file"
            type="file"
            name="file"
            onChange={this.handleFileChange}
          />
          <label htmlFor="file">Choose a file</label>
          <button onClick={this.handleUpload}>Upload</button>
        </form>
        <p>{Math.round(this.state.loaded)}%</p>
      </React.Fragment>
    );

I’ve given it a label, that we’ll play with soon. Math.round is to avoid decimal values.

But this will throw error initially as the state loaded doesn’t even exist.

Initialize the state on a component mount with:

state = { selectedFile: null, loaded: null };

There’s no need of defining a constructor for a state, if you’ve been doing that way.

Here’s how handleUpload is now…

handleUpload = (event) => {
    // define upload
    event.preventDefault();
    const data = new FormData();
    data.append('file', this.state.selectedFile, this.state.selectedFile.name);
    axios
      .post(endpoint, data, {
        onUploadProgress: (ProgressEvent) => {
          this.setState({
            loaded: (ProgressEvent.loaded / ProgressEvent.total) * 100
          });
        }
      })
      .then((res) => {
        console.log(res.statusText);
      });
  };

Unuglifying things…

This is image title

I grabbed CSS from here and moved to index.css.

I also added states uploading, message, and defaultMessage. If it is currently uploading, the loaded state will show the % completed. Otherwise, the message will be shown.

The JSX returned has also changed slightly, the react part and methods remain the same. This comes from that same HTML file.

return (
      <form className="box" onSubmit={this.handleUpload}>
        <input
          type="file"
          name="file-5[]"
          id="file-5"
          className="inputfile inputfile-4"
          onChange={this.handleFileChange}
        />
        <label htmlFor="file-5">
          <figure>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="20"
              height="17"
              viewBox="0 0 20 17"
            >
              <path d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z" />
            </svg>
          </figure>
          <span>
            {this.state.uploading
              ? this.state.loaded + '%'
              : this.state.message}
          </span>
        </label>
        <button className="submit" onClick={this.handleUpload}>
          Upload
        </button>
      </form>
    );

This is image title

After adding some CSS for the button as well, it looks like:

This is image title

If you click on the upload button without choosing a file, it’ll crash.

This is image title

This is because the selected file is null, and null doesn’t have the property of name.

To fix this, return from the handleUpload method if the selectedFile is null. Also, set the message to upload first so the user gets some feedback on what to do.

handleUpload = (event) => {
    event.preventDefault();
    if (this.state.uploading) return;
    if (!this.state.selectedFile) {
      this.setState({ message: 'Select a file first' });
      return;
    }
    this.setState({ uploading: true });
    const data = new FormData();
    data.append('file', this.state.selectedFile, this.state.selectedFile.name);
    axios
      .post(endpoint, data, {
        onUploadProgress: (ProgressEvent) => {
          this.setState({
            loaded: Math.round(
              (ProgressEvent.loaded / ProgressEvent.total) * 100
            )
          });
        }
      })
      .then((res) => {
        this.setState({
          selectedFile: null,
          message: 'Uploaded successfully',
          uploading: false
        });
        console.log(res.statusText);
      });
  };

Also, I’ve rounded the loaded state itself instead of rounding in the output. If you click upload without selecting the file, the message will change to select a file first.

This is image title

You can upload another file before the previous one has finished. We can deal with this by using the unloading state. Just return from the handle upload without uploading if the uploading is true.

if (this.state.uploading) return;
    if (!this.state.selectedFile) {
      this.setState({ message: 'Select a file first' });
      return;
    }
    this.setState({ uploading: true });

After successfully uploading, it changes the message to Uploaded Successfully while uploading state changes to false.

handleFileChange is modified to show the file name that’s going to upload.

handleFileChange = (event) => {
    this.setState({
      selectedFile: event.target.files[0],
      loaded: 0,
      message: event.target.files[0]
        ? event.target.files[0].name
        : this.state.defaultmessage
    });
  };

Error Handling

If you shut down the server, this application will still try to upload the file and loaded will change to NaN (Not a Number). Since uploading was set true, you can’t upload even the server comes back as handleUpload is blocked by this.

Add a catch to the post request…

.catch((err) => {
        this.setState({
          uploading: false,
          message: 'Failed to upload'
        });
      });

Awesome, it uploads a file while displaying the message on error and success.

This is image title

Conclusion

your learn how to basic upload with Reactjs and Nodejs in an easy way to continue next step you can implement validation on client and server or show preview before upload