DEV Community

Cover image for React Uploady with Ant Design
Yoav Niran
Yoav Niran

Posted on

React Uploady with Ant Design

<Intro>

Building a file-upload UI that both looks great and includes features such as: progress indication, cancel uploads, and resume failed uploads, may sound difficult to do. In this article I'd like to dispel this notion and show that in fact can be a breeze.

To achieve said target we'll look into using And Design and React-Uploady.

Ant Design is a great library for UI components. React-Uploady provides excellent building blocks and a lot of functionality for client-side file uploading.

Disclaimer: I'm the creator of React-Uploady

If you're unfamiliar with React-Uploady, I recommend starting at:

The following code example shows how to render an upload queue using UI components from Ant Design (Button, Card, Progress, etc.).

I'll keep the Uploady part simple for the sake of the example but the there's nothing preventing us from using any of its more sophisticated and advanced features/capabilities.

Here's the end result we'll be building toward:

end result

The UI itself may not fit exactly what you're building in your own app, but it demonstrates how easy it is to integrate these two libraries. It was literally a matter of minutes to hook up (and I've never used Ant). If you're using Ant or planning to along with file-upload functionality, you're in luck πŸ™‚.

</Intro>

<Code>

import Uploady from "@rpldy/uploady";
import retryEnhancer from "@rpldy/retry-hooks";

const App = () => {
  return (
    <div className="App">
      <Uploady 
         destination={{ url: "my-server.com/upload" }}
         enhancer={retryEnhancer}>
        <UploadUi />
      </Uploady>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the code above we're setting up the scene, initializing Uploady and rendering the rest of our app.

Notice

We use retryEnhancer to enable upload retry functionality. This isn't mandatory of course but is important as it makes it possible for users to retry failed/aborted uploads.


import { asUploadButton } from "@rpldy/upload-button";
import { Button, PageHeader, Layout } from "antd";

const UploadButton = asUploadButton(Button);

const UploadUi = () => {
  const previewMethodsRef = useRef();
  const [previews, setPreviews] = useState([]);

  const onClearPreviews = useCallback(() => {
    previewMethodsRef.current?.clear();
  }, [previewMethodsRef]);

  return (
    <Layout>
      <PageHeader
        title="File Upload"
        subTitle="Powered by: React Uploady + Ant Design"
        extra={[
          <UploadButton
            key="upload-button"
            extraProps={{
              type: "primary",
              size: "large",
              icon: <CloudUploadOutlined />
            }}
          />,
          <Button
            key="clear-button"
            icon={<DeleteOutlined />}
            size="large"
            disabled={!previews.length}
            onClick={onClearPreviews}
          >
            Clear
          </Button>
        ]}
      />
      <Layout.Content>
        <UploadPreviewCards
          setPreviews={setPreviews}
          previewMethodsRef={previewMethodsRef}
        />
      </Layout.Content>
      <Layout.Footer>Previews Shown: {previews.length}</Layout.Footer>
    </Layout>
  );
};
Enter fullscreen mode Exit fullscreen mode

This is our main UI component, rendering our layout, upload button and previews.

Notice

First thing is we wrap Ant's Button component with Uploady's asUploadButton. This makes it into an Upload Button that will open the file dialog when clicked. Ant specific props are passed using the extraProps prop.

We define previewMethodsRef that is later passed to the UploadPreview Uploady component. It's defined here so we can access the preview API (clear previews method) in this component. More on this later.


import UploadPreview from "@rpldy/upload-preview";
import { Row } from "antd";

const UploadPreviewCards = ({ previewMethodsRef, setPreviews }) => {

  const getPreviewProps = useCallback(
    (item) => ({ id: item.id, name: item.file.name }),
    []
  );

  return (
      <Row gutter={2} justify="center" className="preview-row">
        <UploadPreview
          previewComponentProps={getPreviewProps}
          PreviewComponent={PreviewCard}
          onPreviewsChanged={setPreviews}
          previewMethodsRef={previewMethodsRef}
          rememberPreviousBatches
        />
      </Row>
  );
};
Enter fullscreen mode Exit fullscreen mode

Here we render Uploady's UploadPreview component that makes it easy to add image (and video) previews once upload begins.

Notice

previewComponentProps makes it possible to define which props the custom preview component will receive.

PreviewComponent is our custom preview component that will be rendered for each file being uploaded.

onPreviewsChanged is a callback called when previews are added/removed. We use it to change the state and make it possible to show the number of previews shown (by the parent component in this case).

previewMethodsRef is a React ref that will receive the clear previews method that is used by the parent component (in this case).

rememberPreviousBatches instructs the preview component to keep the previews from previous batches.


import {
  useItemProgressListener,
  useItemFinalizeListener,
  useItemAbortListener,
  useAbortItem
} from "@rpldy/uploady";
import retryEnhancer, { useRetry } from "@rpldy/retry-hooks";
import { Button, Card, Col, Row, Progress, PageHeader, Layout } from "antd";

const STATES = {
  PROGRESS: "PROGRESS",
  DONE: "DONE",
  ABORTED: "ABORTED",
  ERROR: "ERROR"
};

const isItemError = (state) =>
  state === STATES.ABORTED || state === STATES.ERROR;

const PreviewCard = memo(({ id, url, name }) => {
  const [percent, setPercent] = useState(0);
  const [itemState, setItemState] = useState(STATES.PROGRESS);
  const abortItem = useAbortItem();
  const retry = useRetry();

  useItemProgressListener((item) => {
    setPercent(item.completed);
  }, id);

  useItemFinalizeListener((item) => {
    setItemState(
      item.state === "finished"
        ? STATES.DONE
        : item.state === "aborted"
        ? STATES.ABORTED
        : STATES.ERROR
    );
  }, id);

  useItemAbortListener(() => {
    setItemState(STATES.ABORTED);
  }, id);

  const onAbort = useCallback(() => {
    abortItem(id);
  }, [abortItem, id]);

  const onRetry = useCallback(() => {
    retry(id);
  }, [retry, id]);

  return (
    <Col gutter={2}>
      <Card
        hoverable
        style={{ width: 240 }}
        cover={<img alt="example" src={url} />}
        actions={[
          <Button
            key="stop"
            icon={<StopOutlined />}
            onClick={onAbort}
            disabled={itemState !== STATES.PROGRESS}
            type="link"
          />,
          <Button
            key="retry"
            icon={<RedoOutlined />}
            onClick={onRetry}
            disabled={!isItemError(itemState)}
            type="link"
          />
        ]}
      >
        <Card.Meta
          title={name}
          description={
            <Progress
              type="dashboard"
              percent={percent}
              width={66}
              strokeColor={
                isItemError(itemState)
                  ? "#FF4D4F"
                  : {
                      "0%": "#108ee9",
                      "100%": "#87d068"
                    }
              }
              status={isItemError(itemState) ? "exception" : undefined}
            />
          }
        />
      </Card>
    </Col>
  );
});
Enter fullscreen mode Exit fullscreen mode

The Preview Card makes use of different Uploady hooks as well as very useful components from Ant.

Notice

useItemProgressListener is used to get updates on the upload progress for the item being uploaded (we pass the id as the hook's second param so it's only called for the specific item).

useItemFinalizeListener and useItemAbortListener are used to set the state of the item (ex: success, failed, etc.).

useRetry is used to access the retry method and call it (only) on failed items.

useAbortItem is used to access the abort method and call it in order to cancel an upload before it completes.

Ant's Card component comes in handy for our purpose. It shows the image being uploaded (using the cover prop), shows textual information and action buttons that provide the user with the ability to abort/retry.

Ant's Progress component has a "dashboard" variant that looks cool inside the preview card. It accepts a strokeColor prop that makes the item's upload status clearly visible.

</Code>

<Conclusion>

Working code for this post can be found in this sandbox:

Uploady's approach to UI is to be as minimalist as possible. To do what it does best (upload files), while letting developers build their presentation on top of it in any way they like.

Ant Design is a great library for UI components and (from my limited experience) seems very easy to use with plenty of options and features.

Combining the two felt very natural and easy. Hopefully this post comes in handy for you if you're looking to add file-upload capabilities to your application that look great.

</Conclusion>

Top comments (0)