React Uploady with Ant Design
Tags: react, javascript, antd, upload
Published: 2021-04-29 09:39:00

## \<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](https://ant.design/) is a great library for UI components. [React-Uploady](https://github.com/rpldy/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: {% post https://dev.to/poeticgeek/react-uploady-uploading-files-in-react-163f %}
 
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](https://res.cloudinary.com/literarycoder/image/upload/q_auto/v1619611264/content/uploady-antd.gif)


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\>

```javascript
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>
  );
};
```

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

**Notice** 

We use [retryEnhancer](https://github.com/rpldy/react-uploady/tree/master/packages/ui/retry-hooks#enabling-retry) 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.

----
```javascript
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>
  );
};
```

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](https://github.com/rpldy/react-uploady/tree/master/packages/ui/upload-button#custom-upload-button-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](https://github.com/rpldy/react-uploady/tree/master/packages/ui/upload-preview) Uploady component. It's defined here so we can access the preview API (clear previews method) in this component. More on this later.

----
```javascript
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>
  );
};
```

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.

----
``` javascript
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>
  );
});
```

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

**Notice**

[useItemProgressListener](https://github.com/rpldy/react-uploady/tree/master/packages/ui/uploady#useitemprogresslistener-event-hook) 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](https://github.com/rpldy/react-uploady/tree/master/packages/ui/uploady#useitemfinalizelistener-event-hook) and [useItemAbortListener](https://github.com/rpldy/react-uploady/tree/master/packages/ui/uploady#useitemfinalizelistener-event-hook) are used to set the state of the item (ex: success, failed, etc.).

[useRetry](https://github.com/rpldy/react-uploady/tree/master/packages/ui/retry-hooks#useretry) is used to access the retry method and call it (only) on failed items.

[useAbortItem](https://github.com/rpldy/react-uploady/tree/master/packages/ui/uploady#useabortitem) is used to access the abort method and call it in order to cancel an upload before it completes.

Ant's [Card](https://ant.design/components/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](https://ant.design/components/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:

{% codesandbox react-uploady-antd-upload-with-preview-progress-d7o3w %}


[Uploady](https://github.com/rpldy/react-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\>_