Telerik blogs
KendoReactT2_1200x303

A ready-made React dropzone or React upload component can make your life much easier. Let’s take a look!

There are quite a few different situations where users need to be able to upload files on a website. For instance, a social media website might allow a user to upload their profile picture, and an email client needs to enable users to attach and send files to other people. A file can be uploaded using an input element with the type set to file, but let’s be honest, that’s not the best-looking way to upload files.

Fortunately, there are ready-made and feature-rich solutions available for React applications, such as React Dropzone or the React Upload component found in the KendoReact library. In this article, we will cover how to use the latter to create a nice-looking upload functionality.

Source Code

You can find the full code example for this article in this GitHub repository. Below you can also find an interactive StackBlitz example.

Project Setup

If you want to follow the article, you can create a new React project using Vite. Run the command below in your terminal.

$ npm create vite@latest how-to-work-with-the-kendoreact-upload-component

Next, we need to install the dependencies needed for the project and then start the dev server.

$ cd how-to-work-with-the-kendoreact-upload-component
$ npm install @progress/kendo-react-upload @progress/kendo-licensing @progress/kendo-theme-default
$ npm run dev

Finally, let’s do a bit of a cleanup and set up the KendoReact Default theme.

src/App.jsx

import "./App.css";
 
function App() {
  return <div className="App"></div>;
}
 
export default App;

src/App.css

.App {
  max-width: 40rem;
  margin: 2rem auto;
}

src/main.jsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "@progress/kendo-theme-default/dist/all.css";
import "./index.css";
 
ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

That’s it for the cleanup. Let’s have a look at how we can utilize the React Upload component provided by KendoReact.

If you would like to learn more about the Kendo UI themes, check out the Building a Design System with Kendo UI article.

Adding React File Upload Component

The Upload component (also called a dropzone) provided by KendoReact is available in the @progress/kendo-react-upload package, which we installed before. The code snippet below shows how it can be used.

src/App.jsx

import "./App.css";
import { Upload } from "@progress/kendo-react-upload";
 
const saveUrl = "https://demos.telerik.com/kendo-ui/service-v4/upload/save";
const removeUrl = "https://demos.telerik.com/kendo-ui/service-v4/upload/remove";
 
function App() {
  return (
    <div className="App">
      <Upload
        defaultFiles={[]}
        withCredentials={false}
        saveUrl={saveUrl}
        removeUrl={removeUrl}
      />
    </div>
  );
}
 
export default App;

As the next image shows, KendoReact provides us with an elegant upload block.

React Upload component has button for Select files and the whole bar allows drop files here to upload

And here’s the default input file element.

Default File Input has a Choose file button, and currently reports that no file has been chosen

The difference in the UI is staggering, and that’s not all. The React Upload component supports globalization, drag-and-drop and external dropzone functionality, shows the list of uploaded files in a well-formatted list, and allows restricting minimum and maximum size, as well as allowed file types. What’s more, the upload progress is shown for each file, and new files can easily be removed.

KendoReact Uploading and Removing files in React upload dropzone component. Once added, the files show in a list and can easily be X-ed to remove them

Another nice feature is the retry ability. If an upload fails, a retry button can be used to try uploading a file again.

The KendoReact Upload component retry ability in React Upload dropzone component

Let’s see how we can use some of the great features of the React Upload component by KendoReact. Next, we will see how we can disable the upload functionality and define restrictions for uploaded files, such as file types and the minimum and maximum size.

Disabling Upload and Restricting File Types and Sizes

It’s a breeze to disable and configure the size and file type restrictions in this React Upload component. To disable the Upload component, we just need to pass the disabled prop.

src/App.jsx

<Upload
  defaultFiles={[]}
  withCredentials={false}
  saveUrl={saveUrl}
  removeUrl={removeUrl}
  disabled
  />

As the image below shows, the whole Upload component will be grayed out.

React-upload-dropzone-component-disabled-state

The allowed file size can be restricted by passing an object with minFileSize and maxFileSize to the restrictions prop.

src/App.jsx

<Upload
  defaultFiles={[]}
  withCredentials={false}
  saveUrl={saveUrl}
  removeUrl={removeUrl}
  restrictions={{
    minFileSize: 50000,
    maxFileSize: 10000000,
  }}
/>

An error is displayed if a user tries to upload a file that is smaller than minFileSize or bigger than maxFileSize.

File too small error

File types can be restricted by providing allowedExtensions array as part of the restrictions prop object.

src/App.jsx

<Upload
  defaultFiles={[]}
  withCredentials={false}
  saveUrl={saveUrl}
  removeUrl={removeUrl}
  restrictions={{
    minFileSize: 1000,
    maxFileSize: 10000000,
    allowedExtensions: [".jpg", ".png"],
  }}
/>

In the code snippet above, we defined that the Upload component should accept only .jpg and .png extensions. If we try to upload a file with a different extension, an error will be displayed, as shown in the GIF below.

Incorrect file type error says this file type is not supported when a file is dropped onto the upload component

Next, let’s have a look at how we can customize the list of uploaded items.

Customizing Rendered Output With a Preview

By default, the Upload component displays different icons depending on file extensions of uploaded files. But what if we would like to display a preview of the image uploaded by a user? Fortunately, this React Dropzone component lets us override the component that is used to render the uploaded files.

We can do that by passing a prop called listItemUI. It accepts a custom component that will receive a few useful props, such as files, disabled, async and methods for cancelling an upload, retrying and removing a file. Here’s a custom UploadFileItem component.

src/UploadFileItem.jsx

import { UploadFileStatus } from "@progress/kendo-react-upload";
import { useEffect, useState } from "react";
import { getTotalFilesSizeMessage } from "./getTotalFilesSizeMessage";
import { getFileExtensionIcon } from "./getFileExtensionIcon";
 
const errors = {
  invalidFileExtension: "This file type is not supported.",
  invalidMaxFileSize: "This file is too big.",
  invalidMinFileSize: "This file is too small.",
  uploadFailed: "File(s) failed to upload.",
};
 
const getFileError = file => {
  if (file.status === UploadFileStatus.UploadFailed) {
    return errors.uploadFailed;
  }
 
  if (file.validationErrors && file.validationErrors.length) {
    return errors[file.validationErrors[0]];
  }
 
  return "";
};
 
const UploadedFileItem = props => {
  const { files, onRetry, onRemove } = props;
  const [preview, setPreview] = useState(null);
  const file = files?.[0];
  const { status } = file;
 
  const isProgressVisible = status === UploadFileStatus.Uploading;
  const isValidationError =
    file.validationErrors && file.validationErrors.length;
  const isUploadError = status === UploadFileStatus.UploadFailed;
  const isActionVisible =
    isValidationError ||
    [
      UploadFileStatus.Uploaded,
      UploadFileStatus.Initial,
      UploadFileStatus.UploadFailed,
    ].includes(status);
 
  useEffect(() => {
    if (![".jpg", ".jpeg", ".png"].includes(file.extension)) return;
    const rawFile = file.getRawFile();
    // Create a preview of an image when the upload
    const reader = new FileReader();
    const onLoad = e => {
      console.log("on load", e);
      setPreview(e.target.result);
    };
 
    reader.addEventListener("load", onLoad);
    reader.readAsDataURL(rawFile);
    return () => {
      reader.removeEventListener("load", onLoad);
    };
  }, [status, file]);
 
  return (
    <>
      <div className="k-file-single">
        <span className="k-file-group-wrapper">
          {preview ? (
            <span className="k-file-group">
              <img
                style={{ maxWidth: "32px", display: "block" }}
                src={preview}
                alt={`${file.name} preview image`}
              />
            </span>
          ) : (
            <span
              className={`k-file-group k-icon ${getFileExtensionIcon(file)}`}
            ></span>
          )}
        </span>
        <span className="k-file-name-size-wrapper">
          {isUploadError || isValidationError ? (
            <>
              <div className="k-file-name k-file-name-invalid">{file.name}</div>
              <div className="k-file-validation-message k-text-error">
                {getFileError(file)}
              </div>
            </>
          ) : (
            <>
              <span className="k-file-name">{file.name}</span>
              <span className="k-file-size">
                {getTotalFilesSizeMessage([file])}
              </span>
            </>
          )}
        </span>
      </div>
      <strong className="k-upload-status k-d-flex k-align-self-center">
        {isProgressVisible ? (
          <span className="k-upload-pct">{file.progress}%</span>
        ) : null}
        {isUploadError ? (
          <button
            type="button"
            className="k-button k-button-icon k-flat k-upload-action"
            onClick={() => onRetry(file.uid)}
          >
            <span className="k-icon k-retry k-i-refresh-sm" />
          </button>
        ) : null}
        {isActionVisible ? (
          <button
            type="button"
            className="k-button k-button-icon k-flat k-upload-action"
            onClick={() => onRemove(file.uid)}
          >
            <span className="k-icon k-delete k-i-x" />
          </button>
        ) : null}
      </strong>
    </>
  );
};
 
export default UploadedFileItem;

The component comprises a bit of logic and markup since we have to re-implement everything for the file list item ourselves. Basically, the component renders a file’s name, an error, and retry and remove buttons. However, for a file that is of type jpg, jpeg or png, a preview is generated using the FileReader. If there is no preview image, one of the default Kendo UI icons is displayed.

We also need to create getTotalFilesSizeMessage and getFileExtensionIcon helpers. The first one calculates the total size of the image and returns the appropriate size suffix, while the latter is responsible for returning an icon class based on the extension of a file’s extension.

src/getTotalFilesSizeMessage.js

export const getTotalFilesSizeMessage = files => {
  let totalSize = 0;
  let i;
  if (typeof files[0].size === "number") {
    for (i = 0; i < files.length; i++) {
      if (files[i].size) {
        totalSize += files[i].size || 0;
      }
    }
  } else {
    return "";
  }
  totalSize /= 1024;
  if (totalSize < 1024) {
    return totalSize.toFixed(2) + " KB";
  } else {
    return (totalSize / 1024).toFixed(2) + " MB";
  }
};

src/getFileExtensionIcon.js

export const getFileExtensionIcon = file => {
  switch (file.extension) {
    case ".png":
    case ".jpg":
    case ".jpeg":
    case ".tiff":
    case ".bmp":
    case ".gif":
      return "k-i-file-image";
    case ".mp3":
    case ".mp4":
    case ".wav":
      return "k-i-file-audio";
    case ".mkv":
    case ".webm":
    case ".flv":
    case ".gifv":
    case ".avi":
    case ".wmv":
      return "k-i-file-video";
    case ".txt":
      return "k-i-file-txt";
    case ".pdf":
      return "k-i-file-pdf";
    case ".ppt":
    case ".pptx":
      return "k-i-file-presentation";
    case ".csv":
    case ".xls":
    case ".xlsx":
      return "k-i-file-data";
    case ".html":
    case ".css":
    case ".js":
    case ".ts":
      return "k-i-file-programming";
    case ".exe":
      return "k-i-file-config";
    case ".zip":
    case ".rar":
      return "k-i-file-zip";
    default:
      return "k-i-file";
  }
};

Finally, let’s update the App component and pass our newly created UploadFileItem component as a prop to the React Upload component.

src/App.jsx

import "./App.css";
import { Upload } from "@progress/kendo-react-upload";
import UploadFileItem from "./UploadFileItem";
 
const saveUrl = "https://demos.telerik.com/kendo-ui/service-v4/upload/save";
const removeUrl = "https://demos.telerik.com/kendo-ui/service-v4/upload/remove";
 
function App() {
  return (
    <div className="App">
      <Upload
        defaultFiles={[]}
        withCredentials={false}
        saveUrl={saveUrl}
        removeUrl={removeUrl}
        restrictions={{
          minFileSize: 1000,
          maxFileSize: 10000000,
          allowedExtensions: [".jpg", ".png"],
        }}
        listItemUI={UploadFileItem}
      />
    </div>
  );
}
 
export default App;     

The GIF below shows what the Upload component looks like with our custom UploadFileItem component.

Custom listItemUI component with image previews in React upload dropzone

Summary

That’s it! The React Upload component in KendoReact is a great choice for creating nice upload experiences. It has a lot of features out of the box and can be easily modified to suit your needs. What’s more, if you decide to go with KendoReact instead of the open-source alternative, React Dropzone, you also get a full UI suite of 100+ well-crafted components for anything you might need, like forms, charts, tables and more.


Thomas Findlay-2
About the Author

Thomas Findlay

Thomas Findlay is a 5-star rated mentor, full-stack developer, consultant, technical writer and the author of “React - The Road To Enterprise” and “Vue - The Road To Enterprise.” He works with many different technologies such as JavaScript, Vue, React, React Native, Node.js, Python, PHP and more. Thomas has worked with developers and teams from beginner to advanced and helped them build and scale their applications and products. Check out his Codementor page, and you can also find him on Twitter.

Related Posts

Comments

Comments are disabled in preview mode.