Create file uploader in react

Learn how to create custom file uploader component in react.

HTML has input type file which is used to pick a file from the local machine and that can be uploaded to the server.

But native design of this file selector is not so good and it varies for different browsers.

So we will create a custom file uploader component in react which can be reused and we will style it according to our requirement which will provide consistency.

Our component will be composed of three different parts.

  • Input: Input type file, we will its functionality for picking up the file but keep it invisible.
  • Label: This will be an overlay over the input which will be visible to the user.
  • Helper Text: This will be placed aside our file picker to show picked file name or error or some message.

We will be using some extra packages as well for our help.

  • prop-types: To validate the props we will receive inside our component.
  • classnames: With this package we can use the CSS classes as javascript object, To nake this work just name our CSS file as filename.module.css.

Below is the folder structure of our component.

React file uploader component folder structure

File uploader component in react.

Let us begin creating our component.

First, import all the required packages.

import React from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import styles from "./index.module.css";

const FileUploader = props => {
  //Other code will go here...
}

export default FileUploader;

As we don’t need to maintain any state, that is why we will be creating a functional component.

Now before creating the structure of our component, validate the props to make sure we get all the necessary inputs.

FileUploader.propTypes = {
  name: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  readOnly: PropTypes.bool,
  onChange: PropTypes.func,
  /* Will be applied to container */
  className: PropTypes.string,
  /* Will be applied to underlying input tag */
  inputClassName: PropTypes.string,
  /* Will be applied to label */
  labelClassName: PropTypes.string,
  error: PropTypes.bool,
  helperText: PropTypes.string
};

FileUploader.defaultProps = {
  className: "",
  inputClassName: "",
  labelClassName: "",
  label: "Choose File",
  readOnly: false
};

We have also set the default props in case we didn’t received proper inputs.

Now combine the different classes for different parts of the component and create the structure.

const {
    className,
    inputClassName,
    labelClassName,
    label,
    readOnly,
    error,
    helperText
  } = props;

  const _className = cx(styles.uploadBtnWrapper, className);

  const _inputClassName = cx(styles.input, inputClassName);

  const _labelClassName = cx(styles.label, labelClassName, {
    [styles.error]: error,
    [styles.readonly]: readOnly
  });

  const _helperTextClassName = cx(styles.helperText, { [styles.error]: error });

  let _props = {
    disabled: readOnly,
    className: _inputClassName,
    onChange: handleOnChange
  };

  return (
    <div className={_className}>
      {label ? <label className={_labelClassName}>{label}</label> : null}

      <input {..._props} type="file" />

      {helperText && helperText.length ? (
        <span className={_helperTextClassName}>{helperText}</span>
      ) : null}
    </div>
  );

cx is used to club different CSS classes together.

The most important thing pending right now is listening to the change event and passing the data to the parent. Lets do that.

const handleOnChange = event => {
  const { value } = event.target;
  const { onChange, name } = props;
  onChange && onChange({ value, name, event });
};

Complete code of file uploader component.

import React from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import styles from "./index.module.css";

const FileUploader = props => {
  const handleOnChange = event => {
    const { value } = event.target;
    const { onChange, name } = props;
    onChange && onChange({ value, name, event });
  };

  const {
    className,
    inputClassName,
    labelClassName,
    label,
    readOnly,
    error,
    helperText
  } = props;

  const _className = cx(styles.uploadBtnWrapper, className);

  const _inputClassName = cx(styles.input, inputClassName);

  const _labelClassName = cx(styles.label, labelClassName, {
    [styles.error]: error,
    [styles.readonly]: readOnly
  });

  const _helperTextClassName = cx(styles.helperText, { [styles.error]: error });

  let _props = {
    disabled: readOnly,
    className: _inputClassName,
    onChange: handleOnChange
  };

  return (
    <div className={_className}>
      {label ? <label className={_labelClassName}>{label}</label> : null}

      <input {..._props} type="file" />

      {helperText && helperText.length ? (
        <span className={_helperTextClassName}>{helperText}</span>
      ) : null}
    </div>
  );
};

FileUploader.propTypes = {
  name: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  readOnly: PropTypes.bool,
  onChange: PropTypes.func,
  /* Will be applied to container */
  className: PropTypes.string,
  /* Will be applied to underlying input tag */
  inputClassName: PropTypes.string,
  /* Will be applied to label */
  labelClassName: PropTypes.string,
  error: PropTypes.bool,
  helperText: PropTypes.string
};

FileUploader.defaultProps = {
  className: "",
  inputClassName: "",
  labelClassName: "",
  label: "Choose File",
  readOnly: false
};

export default FileUploader;

Amazing, we have finished structure and functioning of our component. Let’s start styling it.

As I had already mentioned above we will be hiding the actual input tag and will be replacing it with an overlay, that is why we have placed our component inside a wrapper so that we can style them efficiently.

Complete CSS code for file picker.

//index.module.css
.uploadBtnWrapper {
  display: inline-flex;
  margin: 5px 0px;
  overflow: hidden;
  position: relative;
  justify-content: center;
  align-items: center;
  margin: 10px;
}

label {
  border: 1px solid;
  color: #8bc34a;
  background-color: #fff;
  padding: 5px 10px;
  border-radius: 4px;
  font-size: 16px;
  font-weight: 600;
}

input[type="file"] {
  font-size: 100px;
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  margin: 0;
}

.readonly {
  color: #eee !important;
}

.helperText {
  margin-left: 10px;
  font-weight: 600;
  font-size: 14px;
  text-transform: capitalize;
  letter-spacing: 0.3px;
  position: relative;
  color: #7cb342;
}

.error {
  color: #eb5055;
}

Input

ReactDOM.render(
  <div className="abc">
    <FileUploader name="file" />
    <FileUploader
      name="error"
      label="Upload"
      helperText="There is some error"
      error={true}
    />
    <FileUploader name="disabled" label="Disabled" readOnly={true} />
  </div>,
  document.getElementById("root")
);

Output

React file uploader