Responsive React File Upload Component With Drag And Drop
Tags: react, tutorial, javascript
Published: 2021-01-03 04:44:29

While working on a React project, I implemented a **responsive file upload component** that supports **drag and drop without using any libraries**. Most of the file upload components online used libraries such as [react-dropzone](https://www.npmjs.com/package/react-dropzone) to support drag and drop. So, I thought I'd share how I made the component and show a typical use case for it.

### End result

<figure>
<img src="https://dev-to-uploads.s3.amazonaws.com/i/x2kisq8adak60yso498d.png"/>  <figcaption>Figure 1: File upload component used in a form (example use case)</figcaption>
</figure>

<figure>
<img src="https://dev-to-uploads.s3.amazonaws.com/i/028yeaiio875k7r4mv5t.png"/>  <figcaption>Figure 2: Responsive file upload component</figcaption>
</figure>

The **features** include:
- drag and drop without using any libraries
- displaying image preview for image files
- displaying file size & name
- removing files in the "To Upload" section
- preventing user from uploading files bigger than a specified size
 - Note: this should also be done on the backend for security reasons

### Project Setup
Prerequisite: [Node](https://nodejs.org/en/download/) (for installing npm packages) 

If you are familiar with building React applications, the easiest way to set up a new React project is by using [create-react-app](https://github.com/facebook/create-react-app). So, run the following commands in a terminal/command-line:
```
npx create-react-app react-file-upload
cd react-file-upload
```
To ensure everything was set up properly after you run ``npm start``, the following should appear once you visit ``localhost:3000`` in a browser:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bvd8uirtbb2d0pitprov.png)

Before building the component, let's modify and remove some files to get rid of unnecessary code.

- Change ``App.js`` to the following:
```
import React from 'react';

function App() {
  return (
    <div></div>
  );
}

export default App;
```

- Change ``index.js`` to the following:
```
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
```

Remove all files in the ``src`` folder except
- ``App.js``
- ``index.js`` 
- ``index.css``

### File Upload Component

#### Installing Dependencies

The dependencies we will need are: 

[styled-components](https://www.npmjs.com/package/styled-components)
- For styling the component
 - styled components allow for style encapsulation and creating 
   dynamic styles via props 

[node-sass](https://www.npmjs.com/package/node-sass)
- For compiling Sass styles used in styled components (Optional, can use CSS)

To install them, run ``npm i styled-components node-sass``.

#### Folder Structure

A good convention for structuring folders and files is to create a components folder that has a folder for each component.  This makes it easier to find the logic and styles for each component. 

Following this convention, **create a components folder** in the ``src`` folder and then a **file-upload folder** within the ``components`` folder. 

Lastly, within the file-upload folder, create 2 new files.
- ``file-upload.component.jsx``
- ``file-upload.styles.js``

#### State

Since we are creating a functional component and need to use state, we will use the [useState hook](https://reactjs.org/docs/hooks-reference.html#usestate).

The useState hook returns a stateful value which is the same as the value passed as the first argument, and a function to update it.

For our purposes, we will need state to keep track of the uploaded files. So, in the ``file-upload.component.jsx`` file, add the following:

```
import React, { useState } from "react";

const FileUpload = () => {
  const [files, setFiles] = useState({});

  return (
   <div></div>
  )
}

export default FileUpload;
```
**“Shouldn't we use an empty array instead of an empty object for the ``files`` state?”**

Using an object will allow us to easily manipulate (add/remove) the ``files`` state and prevent files with the same name from being uploaded more than once. Here is an example of how the ``files`` state will look like:

```
{
 "file1.png": File,
 "file2.png": File
}
```
If we used an array it would require more work. For instance, to remove a file we would have to iterate through each file until we find the one to remove.

Note: File is a JS object. More info can be found at https://developer.mozilla.org/en-US/docs/Web/API/File.

#### useRef hook

If you look at Figure 1 above, you will notice the user can either drag and drop files or press the Upload Files button. By default, an [file input tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file) will open the file explorer once it is clicked. However, we want to open it once the Upload Files button is clicked so we will require a DOM reference to the file input tag.

To create a DOM reference, we will use the [useRef hook](https://reactjs.org/docs/hooks-reference.html#useref). The useRef hook returns a mutable ref object whose ``.current`` property refers to a DOM node (file input tag in this case).

Once we use the useRef hook, we must pass the returned value to the ref attribute of the file input tag, like so:

```
import React, { useState, useRef } from "react";

const FileUpload = (props) => {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  return (
   <input type="file" ref={fileInputField} />
  )
}

export default FileUpload;
```

#### Props

The component will have the following props:

- ``label``
 - Determines the label of the component (e.g. "Profile Image(s)" in Figure 1 above)
- ``maxFileSizeInBytes``
 - For preventing files above the specified size from being uploaded
- ``updateFilesCb``
 - A callback function used for sending the ``files`` state to the parent component

**“Why do we need to send the ``files`` state to the parent component?”**

Typically, the file upload component will be used in a form and when working with forms in React, the component stores the form data in the state. Thus, for the parent component to also store the uploaded files, we need the file upload component to send it.

**“Why do we need use a callback function to send the ``files`` state to the parent component?”**

Since React has unidirectional data flow, we cannot easily pass data from the child component (file upload component) to the parent component. As a workaround, we will pass a function declared in the parent component and the file upload component will call that function with the ``files`` state as an argument. This process of sending data from the child to the parent can be further explained at https://medium.com/@jasminegump/passing-data-between-a-parent-and-child-in-react-deea2ec8e654.

Using [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), we can now add the props like so:
```
import React, { useRef, useState } from "react";

const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) => {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  return (
   <input type="file" ref={fileInputField} />
  )
}

export default FileUpload;
```

**“Why are we using the spread syntax when destructuring ``otherProps``?”**

When destructuring, we can assign all other values that were not explicitly destructured to a variable.

```
let props = { a: 1, b: 2, c: 3};
let {a, ...otherProps} = props;

//a = 1
//otherProps = {b: 2, c: 3};
```

In this case, for any props that we do not destructure, they will be assigned to the ``otherProps`` variable. We will see the use of this ``otherProps`` variable later.

#### HTML

For the icons shown in Figure 1, we will be using Font Awesome. To import it, add the following in the [head tag](https://www.w3schools.com/tags/tag_head.asp#:~:text=The%20element%20is%20a,scripts%2C%20and%20other%20meta%20information.) in the ``public/index.html`` file:
```
<link
 rel="stylesheet"
 href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css"
/>
```
From Figure 1, it is evident we can split the HTML for the component into 2 main parts. 

![Main parts of component](https://dev-to-uploads.s3.amazonaws.com/i/k3pfntgvbqewsd2w0mpn.png)

Here is the component with the HTML for the first part:
```
import React, { useRef, useState } from "react";

const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) => {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  return (
      <section>
        <label>{label}</label>
        <p>Drag and drop your files anywhere or</p>
        <button type="button">
          <i className="fas fa-file-upload" />
          <span> Upload {otherProps.multiple ? "files" : "a file"}</span>
        </button>
        <input
          type="file"
          ref={fileInputField}
          title=""
          value=""
          {...otherProps}
        />
      </section>      
  );
}

export default FileUpload;
```
Earlier, we discussed that any props that we don't destructure will be assigned to the ``otherProps`` variable (i.e. any prop other than ``label``, ``updateFilesCb``, ``maxFileSizeInBytes``). In the code above, we are taking that ``otherProps`` variable and passing it to the file input tag. This was done so that we can add [attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file) to the file input tag from the parent component via props. 

**“Why are we setting the title and value attribute to ``""``?”**

Setting the title attribute to ``""`` gets rid of the text that shows up by default when hovering over the input tag ("No file chosen").

Setting the value attribute to ``""`` fixes an edge case where uploading a file right after removing it does not change the ``files`` state. Later, we will see that the ``files`` state only changes once the value of the input tag changes. This bug occurs because when we remove a file, the input tag's value does not change. Since state changes re-render HTML, setting the value attribute to ``""`` resets the input tag's value on each ``files`` state change.

Before we write the HTML for the second part, keep in mind that React only allows for [returning one parent element](https://reactjs.org/docs/fragments.html#short-syntax) from a component. Thus, we will enclose both parts in a ``<></>`` tag.

Here is the component with the HTML for both parts:
```
import React, { useRef, useState } from "react";

const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const KILO_BYTES_PER_BYTE = 1000;

const convertBytesToKB = (bytes) => Math.round(bytes / KILO_BYTES_PER_BYTE);

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) => {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  return (
    <>
      <section>
        <label>{label}</label>
        <p>Drag and drop your files anywhere or</p>
        <button type="button">
          <i className="fas fa-file-upload" />
          <span> Upload {otherProps.multiple ? "files" : "a file"}</span>
        </button>
        <input
          type="file"
          ref={fileInputField}
          title=""
          value=""
          {...otherProps}
        />
      </section>

      {/*second part starts here*/}
      <article>
        <span>To Upload</span>
        <section>
          {Object.keys(files).map((fileName, index) => {
            let file = files[fileName];
            let isImageFile = file.type.split("/")[0] === "image";
            return (
              <section key={fileName}>
                <div>
                  {isImageFile && (
                    <img
                      src={URL.createObjectURL(file)}
                      alt={`file preview ${index}`}
                    />
                  )}
                  <div isImageFile={isImageFile}>
                    <span>{file.name}</span>
                    <aside>
                      <span>{convertBytesToKB(file.size)} kb</span>
                      <i className="fas fa-trash-alt" />
                    </aside>
                  </div>
                </div>
              </section>
            );
          })}
        </section>
      </article>
    </>
  );
};

export default FileUpload;
```
In the second part of the HTML, we are iterating through each file in the ``files`` state and displaying the file name, size in KB, and an image preview if the file type is ``image/*`` (i.e. png, jpg...etc).

To display an image preview, we are using the ``URL.createObjectURL`` function. The createObjectURL function takes an object, which in this case is a [File](https://developer.mozilla.org/en-US/docs/Web/API/File) object, and returns a temporary URL for accessing the file. We can then set that URL to ``src`` attribute of an img tag.

#### Styling

We will now use the styled-components package we installed earlier.

Add the following in the ``file-upload.styles.js`` file:
```
import styled from "styled-components";

export const FileUploadContainer = styled.section`
  position: relative;
  margin: 25px 0 15px;
  border: 2px dotted lightgray;
  padding: 35px 20px;
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: white;
`;

export const FormField = styled.input`
  font-size: 18px;
  display: block;
  width: 100%;
  border: none;
  text-transform: none;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  opacity: 0;

  &:focus {
    outline: none;
  }
`;

export const InputLabel = styled.label`
  top: -21px;
  font-size: 13px;
  color: black;
  left: 0;
  position: absolute;
`;

export const DragDropText = styled.p`
  font-weight: bold;
  letter-spacing: 2.2px;
  margin-top: 0;
  text-align: center;
`;

export const UploadFileBtn = styled.button`
  box-sizing: border-box;
  appearance: none;
  background-color: transparent;
  border: 2px solid #3498db;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 1.1em 2.8em;
  text-align: center;
  text-transform: uppercase;
  font-weight: 700;
  border-radius: 6px;
  color: #3498db;
  position: relative;
  overflow: hidden;
  z-index: 1;
  transition: color 250ms ease-in-out;
  font-family: "Open Sans", sans-serif;
  width: 45%;
  display: flex;
  align-items: center;
  padding-right: 0;
  justify-content: center;

  &:after {
    content: "";
    position: absolute;
    display: block;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    height: 100%;
    background: #3498db;
    z-index: -1;
    transition: width 250ms ease-in-out;
  }

  i {
    font-size: 22px;
    margin-right: 5px;
    border-right: 2px solid;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    width: 20%;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  @media only screen and (max-width: 500px) {
    width: 70%;
  }

  @media only screen and (max-width: 350px) {
    width: 100%;
  }

  &:hover {
    color: #fff;
    outline: 0;
    background: transparent;

    &:after {
      width: 110%;
    }
  }

  &:focus {
    outline: 0;
    background: transparent;
  }

  &:disabled {
    opacity: 0.4;
    filter: grayscale(100%);
    pointer-events: none;
  }
`;

export const FilePreviewContainer = styled.article`
  margin-bottom: 35px;

  span {
    font-size: 14px;
  }
`;

export const PreviewList = styled.section`
  display: flex;
  flex-wrap: wrap;
  margin-top: 10px;

  @media only screen and (max-width: 400px) {
    flex-direction: column;
  }
`;

export const FileMetaData = styled.div`
  display: ${(props) => (props.isImageFile ? "none" : "flex")};
  flex-direction: column;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 10px;
  border-radius: 6px;
  color: white;
  font-weight: bold;
  background-color: rgba(5, 5, 5, 0.55);

  aside {
    margin-top: auto;
    display: flex;
    justify-content: space-between;
  }
`;

export const RemoveFileIcon = styled.i`
  cursor: pointer;

  &:hover {
    transform: scale(1.3);
  }
`;

export const PreviewContainer = styled.section`
  padding: 0.25rem;
  width: 20%;
  height: 120px;
  border-radius: 6px;
  box-sizing: border-box;

  &:hover {
    opacity: 0.55;

    ${FileMetaData} {
      display: flex;
    }
  }

  & > div:first-of-type {
    height: 100%;
    position: relative;
  }

  @media only screen and (max-width: 750px) {
    width: 25%;
  }

  @media only screen and (max-width: 500px) {
    width: 50%;
  }

  @media only screen and (max-width: 400px) {
    width: 100%;
    padding: 0 0 0.4em;
  }
`;

export const ImagePreview = styled.img`
  border-radius: 6px;
  width: 100%;
  height: 100%;
`;
```

When using styled-components, we are creating components that render an HTML tag with some styles. For example, the ``ImagePreview`` is a component that renders an ``img`` tag with the styles inside the [tagged template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). 

Since we are creating components, we can pass props to it and access it when writing the styles (e.g. ``FileMetaData`` in the example above). 

We have now finished the styling and adding drag and drop.

**“But wait, when did we add drag and drop?”**

By default, the file input tag supports drag and drop. We simply just styled the input tag and made it [absolutely positioned](https://www.w3schools.com/css/css_positioning.asp) (refer to ``FormField`` above). 

To use the styles we wrote, import all the styled components and replace the HTML in the ``file-upload.component.jsx`` file.
```
import React, { useRef, useState } from "react";
import {
  FileUploadContainer,
  FormField,
  DragDropText,
  UploadFileBtn,
  FilePreviewContainer,
  ImagePreview,
  PreviewContainer,
  PreviewList,
  FileMetaData,
  RemoveFileIcon,
  InputLabel
} from "./file-upload.styles";

const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const KILO_BYTES_PER_BYTE = 1000;

const convertBytesToKB = (bytes) =>
  Math.round(bytes / KILO_BYTES_PER_BYTE);

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) => {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

    return (
    <>
      <FileUploadContainer>
        <InputLabel>{label}</InputLabel>
        <DragDropText>Drag and drop your files anywhere or</DragDropText>
        <UploadFileBtn type="button">
          <i className="fas fa-file-upload" />
          <span> Upload {otherProps.multiple ? "files" : "a file"}</span>
        </UploadFileBtn>
        <FormField
          type="file"
          ref={fileInputField}
          title=""
          value=""
          {...otherProps}
        />
      </FileUploadContainer>
      <FilePreviewContainer>
        <span>To Upload</span>
        <PreviewList>
          {Object.keys(files).map((fileName, index) => {
            let file = files[fileName];
            let isImageFile = file.type.split("/")[0] === "image";
            return (
              <PreviewContainer key={fileName}>
                <div>
                  {isImageFile && (
                    <ImagePreview
                      src={URL.createObjectURL(file)}
                      alt={`file preview ${index}`}
                    />
                  )}
                  <FileMetaData isImageFile={isImageFile}>
                    <span>{file.name}</span>
                    <aside>
                      <span>{convertBytesToKB(file.size)} kb</span>
                      <RemoveFileIcon
                        className="fas fa-trash-alt"
                      />
                    </aside>
                  </FileMetaData>
                </div>
              </PreviewContainer>
            );
          })}
        </PreviewList>
      </FilePreviewContainer>
    </>
  );
}

export default FileUpload;
```

#### Functionality

We are almost finished with the file-upload component, we just need to add functions so that ``files`` state can be modified.

Earlier we created a DOM reference using the useRef hook. We will now use that to open the file explorer once the "Upload Files" button is clicked. To do this, add the following function within the component:
```
const handleUploadBtnClick = () => {
  fileInputField.current.click();
};
```
We also need to add an ``onClick`` attribute to the ``UploadFileBtn`` component to trigger the function above.
```
<UploadFileBtn type="button" onClick={handleUploadBtnClick}>
```

To process the files selected by the user once the "Upload Files" button is clicked, we need to add an ``onChange`` attribute to the ``FormField`` component.
```
<FormField
  type="file"
  ref={fileInputField}
  onChange={handleNewFileUpload}
  title=""
  value=""
  {...otherProps}
/>
```
Like with any DOM event (e.g. ``onClick``), the function to handle the event will have access to the [event](https://developer.mozilla.org/en-US/docs/Web/API/Event) object. So, the ``handleNewFileUpload`` function will have the event object as its first parameter.
```
 const handleNewFileUpload = (e) => {
    const { files: newFiles } = e.target;
    if (newFiles.length) {
      let updatedFiles = addNewFiles(newFiles);
      setFiles(updatedFiles);
      callUpdateFilesCb(updatedFiles);
    }
  };
```
In the function above, we access the files selected by the user from the ``e.target.files`` property then pass it to a function called ``addNewFiles``. Then, we take the return value from ``addNewFiles`` and pass it to the ``setFiles`` to update the ``files`` state. Since any changes to the ``files`` state must be sent to the parent component, we need to call the ``callUpdateFilesCb`` function. 

The ``addNewFiles`` function takes a [FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList) object (``e.target.files`` above returns a FileList), iterates through it, and returns an object where the key is the file name and the value is the File object.

```
  const addNewFiles = (newFiles) => {
    for (let file of newFiles) {
      if (file.size <= maxFileSizeInBytes) {
        if (!otherProps.multiple) {
          return { file };
        }
        files[file.name] = file;
      }
    }
    return { ...files };
  };
```

**“Why are checking if there is not a ``multiple`` property in ``otherProps``?”**

As explained earlier, we are using the ``otherProps`` variable to add attributes to the file input tag. So, if we don't pass a [``multiple``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-multiple) prop to the file upload component, then the file input tag does not allow for selecting multiple files. Put simply, if there is a ``multiple`` prop, selected files will get added to the ``files`` state. Otherwise, selecting a new file will remove the previous ``files`` state and replace it with the newly selected file. 

The ``callUpdateFilesCb`` function takes the value returned from ``addNewFiles``, converts the ``files`` state to an array and calls the ``updateFilesCb`` function (from the props). 

**“Why do we pass ``updatedFiles`` to ``callUpdateFilesCb`` when we could just use the ``files`` state within the function?”**

Since React state updates are asynchronous, there is no guarantee that when the ``callUpdateFilesCb`` gets called, the ``files`` state will have changed. 

"Why do we have to convert the ``files`` state to an array?"

We don't! However, when uploading files in the ``files`` state to some third party service (e.g. Firebase Cloud Storage), it's easier to work with arrays.

```
const convertNestedObjectToArray = (nestedObj) =>
  Object.keys(nestedObj).map((key) => nestedObj[key]);

const callUpdateFilesCb = (files) => {
  const filesAsArray = convertNestedObjectToArray(files);
  updateFilesCb(filesAsArray);
};
```

To remove a file, we first need to add an ``onClick`` attribute to the ``RemoveFileIcon`` component.

```
<RemoveFileIcon
  className="fas fa-trash-alt"
  onClick={() => removeFile(fileName)}
/>
``` 

The ``removeFile`` function will take a file name, delete it from the ``files`` state, update the ``files`` state, and inform the parent component of the changes. 

```
const removeFile = (fileName) => {
  delete files[fileName];
  setFiles({ ...files });
  callUpdateFilesCb({ ...files });
};
```

Here is the component with all the functions above:
```
import React, { useRef, useState } from "react";
import {
  FileUploadContainer,
  FormField,
  DragDropText,
  UploadFileBtn,
  FilePreviewContainer,
  ImagePreview,
  PreviewContainer,
  PreviewList,
  FileMetaData,
  RemoveFileIcon,
  InputLabel
} from "./file-upload.styles";

const KILO_BYTES_PER_BYTE = 1000;
const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;

const convertNestedObjectToArray = (nestedObj) =>
  Object.keys(nestedObj).map((key) => nestedObj[key]);

const convertBytesToKB = (bytes) => Math.round(bytes / KILO_BYTES_PER_BYTE);

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) => {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  const handleUploadBtnClick = () => {
    fileInputField.current.click();
  };

  const addNewFiles = (newFiles) => {
    for (let file of newFiles) {
      if (file.size < maxFileSizeInBytes) {
        if (!otherProps.multiple) {
          return { file };
        }
        files[file.name] = file;
      }
    }
    return { ...files };
  };

  const callUpdateFilesCb = (files) => {
    const filesAsArray = convertNestedObjectToArray(files);
    updateFilesCb(filesAsArray);
  };

  const handleNewFileUpload = (e) => {
    const { files: newFiles } = e.target;
    if (newFiles.length) {
      let updatedFiles = addNewFiles(newFiles);
      setFiles(updatedFiles);
      callUpdateFilesCb(updatedFiles);
    }
  };

  const removeFile = (fileName) => {
    delete files[fileName];
    setFiles({ ...files });
    callUpdateFilesCb({ ...files });
  };

  return (
    <>
      <FileUploadContainer>
        <InputLabel>{label}</InputLabel>
        <DragDropText>Drag and drop your files anywhere or</DragDropText>
        <UploadFileBtn type="button" onClick={handleUploadBtnClick}>
          <i className="fas fa-file-upload" />
          <span> Upload {otherProps.multiple ? "files" : "a file"}</span>
        </UploadFileBtn>
        <FormField
          type="file"
          ref={fileInputField}
          onChange={handleNewFileUpload}
          title=""
          value=""
          {...otherProps}
        />
      </FileUploadContainer>
      <FilePreviewContainer>
        <span>To Upload</span>
        <PreviewList>
          {Object.keys(files).map((fileName, index) => {
            let file = files[fileName];
            let isImageFile = file.type.split("/")[0] === "image";
            return (
              <PreviewContainer key={fileName}>
                <div>
                  {isImageFile && (
                    <ImagePreview
                      src={URL.createObjectURL(file)}
                      alt={`file preview ${index}`}
                    />
                  )}
                  <FileMetaData isImageFile={isImageFile}>
                    <span>{file.name}</span>
                    <aside>
                      <span>{convertBytesToKB(file.size)} kb</span>
                      <RemoveFileIcon
                        className="fas fa-trash-alt"
                        onClick={() => removeFile(fileName)}
                      />
                    </aside>
                  </FileMetaData>
                </div>
              </PreviewContainer>
            );
          })}
        </PreviewList>
      </FilePreviewContainer>
    </>
  );
};

export default FileUpload;
```

#### Use Case

Let's now use the file upload component in App component to see it in action!

In the ``App.js`` file, we will create a simple form and add state to store the form data.

```
import React, { useState } from "react";

function App() {
  const [newUserInfo, setNewUserInfo] = useState({
    profileImages: []
  });

  const handleSubmit = (event) => {
    event.preventDefault();
    //logic to create a new user...
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <button type="submit">Create New User</button>
      </form>
    </div>
  );
}
export default App;
```

Now to add the file upload component.

```
import React, { useState } from "react";
import FileUpload from "./components/file-upload/file-upload.component";

function App() {
  const [newUserInfo, setNewUserInfo] = useState({
    profileImages: []
  });

  const handleSubmit = (event) => {
    event.preventDefault();
    //logic to create a new user...
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <FileUpload
          accept=".jpg,.png,.jpeg"
          label="Profile Image(s)"
          multiple
        />
        <button type="submit">Create New User</button>
      </form>
    </div>
  );
}

export default App;
```

Notice we have not added the ``updateFilesCb`` prop yet. Before we can do that, we need to create a function that updates only the ``profileImages`` property of the ``newUserInfo`` state.

```
const updateUploadedFiles = (files) =>
    setNewUserInfo({ ...newUserInfo, profileImages: files });
```

We will now pass this function as the ``updateFilesCb`` prop. So, any time the ``files`` state changes in the file upload component, it will be saved in the ``profileImages`` property of the ``newUserInfo`` state.

```
import React, { useState } from "react";
import FileUpload from "./components/file-upload/file-upload.component";

function App() {
  const [newUserInfo, setNewUserInfo] = useState({
    profileImages: []
  });

  const updateUploadedFiles = (files) =>
    setNewUserInfo({ ...newUserInfo, profileImages: files });

  const handleSubmit = (event) => {
    event.preventDefault();
    //logic to create new user...
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <FileUpload
          accept=".jpg,.png,.jpeg"
          label="Profile Image(s)"
          multiple
          updateFilesCb={updateUploadedFiles}
        />
        <button type="submit">Create New User</button>
      </form>
    </div>
  );
}

export default App;
```

**“Why are we passing the ``accept`` and ``multiple`` prop to the file upload component?”**

Since any additional props will get passed to the file input tag, the file input tag will have an ``accept`` and ``multiple`` attribute. 

The [``multiple``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file) attribute allows a user to select multiple files in the file explorer. 

The [``accept``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file) attribute prevents users from selecting file types different from the ones specified (i.e. jpg, png, jpeg in this case). 

Now that we are finished, run ``npm start`` and visit localhost:3000. The following should appear:

![Using the file upload component in a form](https://dev-to-uploads.s3.amazonaws.com/i/1npgfxlibqdex9o2c8tm.png)

For reference, the code can be found at
https://github.com/Chandra-Panta-Chhetri/react-file-upload-tutorial.