I am using ant design components and I have an upload input:
https://ant.design/components/upload/
According to the documentation, action is required on the props.
However I dont need the file to be posted to an url when uploaded, I need the entire FORM to be submited to a rest endpoint (check handlesubmit function)
Trying to go through the documentation, I used the handlechange event to add the file to the state, but the STATUS is never done, so that line is never hit.
What am I missing here?
import React, { Component } from 'react';
import { Input, Upload , Icon, message} from 'antd';
import Form from '../../components/uielements/form';
import Checkbox from '../../components/uielements/checkbox';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
const FormItem = Form.Item;
class RegisterTenantForm extends Component {
constructor(props) {
super(props);
this.state = {TenantId: '', TenantUrl: '', CertificatePassword: '', confirmDirty: false, loading: false, buttondisabled: true};
this.handleChangeTenantUrl = this.handleChangeTenantUrl.bind(this);
this.handleChangeCertificatePassword = this.handleChangeCertificatePassword.bind(this);
this.handleChangeTenantId= this.handleChangeTenantId.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleupload = this.handleupload.bind(this);
this.handleTenantIdValidation = this.handleTenantIdValidation.bind(this);
this.handleTenantAdminUrl = this.handleTenantAdminUrl.bind(this);
};
handleChangeTenantUrl(event){
this.setState({TenantUrl: event.target.value});
}
handleChangeCertificatePassword(event){
this.setState({CertificatePassword: event.target.value});
}
handleChangeTenantId(event){
this.setState({TenantId: event.target.value});
}
beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
if (!isJPG) {
message.error('You can only upload JPG file!');
}
}
handleupload(info){
//let files = e.target.files;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
this.setState({ loading: false });
this.setState({ 'selectedFile': info.file });
}
}
handleTenantIdValidation(rule, value, callback){
const form = this.props.form;
const str = form.getFieldValue('tenantid');
var re = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (str && !str.match(re)) {
this.setState({buttondisabled: true});
callback('Tenant id is not correctly formated id');
}
else {
this.setState({buttondisabled: false});
callback();
}
}
handleTenantAdminUrl(rule, value, callback){
const form = this.props.form;
const str = form.getFieldValue('tenantadminurl');
var re = /^(?:http(s)?://)?[w.-]+(?:.[w.-]+)+[w-._~:/?#[]@!$&'()*+,;=.]+$/i;
if (str && !str.match(re)) {
this.setState({buttondisabled: true});
callback('Tenant Url is not correctly formated id');
}
else {
this.setState({buttondisabled: false});
callback();
}
}
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
/*Notification(
'success',
'Received values of form',
JSON.stringify(values)
);*/
let data = new FormData();
//Append files to form data
data.append("model", JSON.stringify({ "TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "CertificatePassword": this.state.CertificatePassword }));
//data.append("model", {"TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "TenantPassword": this.state.TenantPassword });
let files = this.state.selectedFile;
for (let i = 0; i < files.length; i++) {
data.append("file", files[i], files[i].name);
}
const options = {
method: 'put',
body: data,
config: {
headers: {
'Content-Type': 'multipart/form-data'
}
}
};
adalApiFetch(fetch, "/Tenant", options)
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ data: responseJson });
}
})
.catch(error => {
console.error(error);
});
}
});
}
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Tenant Id" hasFeedback>
{getFieldDecorator('tenantid', {
rules: [
{
required: true,
message: 'Please input your tenant id',
},
{
validator: this.handleTenantIdValidation
}],
})(<Input name="tenantid" id="tenantid" onChange={this.handleChangeTenantId}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Certificate Password" hasFeedback>
{getFieldDecorator('certificatepassword', {
rules: [
{
required: true,
message: 'Please input your password!',
}
],
})(<Input type="password" name="certificatepassword" id="certificatepassword" onChange={this.handleChangeCertificatePassword}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Tenant admin url" hasFeedback>
{getFieldDecorator('tenantadminurl', {
rules: [
{
required: true,
message: 'Please input your tenant admin url!',
},
{
validator: this.handleTenantAdminUrl
}],
})(<Input name="tenantadminurl" id="tenantadminurl" onChange={this.handleChangeTenantUrl} />)}
</FormItem>
<FormItem {...formItemLayout} label="Certificate File">
<Upload onChange={this.handleupload} beforeUpload={this.beforeUpload}>
<Button >
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
</FormItem>
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit" disabled={this.state.buttondisabled}>
Register tenant
</Button>
</FormItem>
</Form>
);
}
}
const WrappedRegisterTenantForm = Form.create()(RegisterTenantForm);
export default WrappedRegisterTenantForm;
Classic mode. File selection dialog pops up when upload button is clicked.
import { Upload, message, Button, Icon } from 'antd';
const props = {
name: 'file',
action: '//jsonplaceholder.typicode.com/posts/',
headers: {
authorization: 'authorization-text',
},
onChange(info) {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
message.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
};
ReactDOM.render(
<Upload {...props}>
<Button>
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
, mountNode);
Use defaultFileList
for uploaded files when page init.
import { Upload, Button, Icon } from 'antd';
const props = {
action: '//jsonplaceholder.typicode.com/posts/',
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
}
},
defaultFileList: [{
uid: 1,
name: 'xxx.png',
status: 'done',
reponse: 'Server Error 500', // custom error message to show
url: 'http://www.baidu.com/xxx.png',
}, {
uid: 2,
name: 'yyy.png',
status: 'done',
url: 'http://www.baidu.com/yyy.png',
}, {
uid: 3,
name: 'zzz.png',
status: 'error',
reponse: 'Server Error 500', // custom error message to show
url: 'http://www.baidu.com/zzz.png',
}],
};
ReactDOM.render(
<Upload {...props}>
<Button>
<Icon type="upload" /> Upload
</Button>
</Upload>
, mountNode);
You can gain full control over filelist by configuring fileList
. You can accomplish all kinds of customed functions. The following shows three circumstances:
1) limit the number of uploaded files.
2) read from response and show file link.
3) filter successfully uploaded files according to response from server.
import { Upload, Button, Icon } from 'antd';
class MyUpload extends React.Component {
state = {
fileList: [{
uid: -1,
name: 'xxx.png',
status: 'done',
url: 'http://www.baidu.com/xxx.png',
}],
}
handleChange = (info) => {
let fileList = info.fileList;
// 1. Limit the number of uploaded files
// Only to show two recent uploaded files, and old ones will be replaced by the new
fileList = fileList.slice(-2);
// 2. read from response and show file link
fileList = fileList.map((file) => {
if (file.response) {
// Component will show file.url as link
file.url = file.response.url;
}
return file;
});
// 3. filter successfully uploaded files according to response from server
fileList = fileList.filter((file) => {
if (file.response) {
return file.response.status === 'success';
}
return true;
});
this.setState({ fileList });
}
render() {
const props = {
action: '//jsonplaceholder.typicode.com/posts/',
onChange: this.handleChange,
multiple: true,
};
return (
<Upload {...props} fileList={this.state.fileList}>
<Button>
<Icon type="upload" /> upload
</Button>
</Upload>
);
}
}
ReactDOM.render(<MyUpload />, mountNode);
If uploaded file is a picture, the thumbnail can be shown. IE8/9
do not support local thumbnail show. Please use thumbUrl
instead.
import { Upload, Button, Icon } from 'antd';
const fileList = [{
uid: -1,
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}, {
uid: -2,
name: 'yyy.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}];
const props = {
action: '//jsonplaceholder.typicode.com/posts/',
listType: 'picture',
defaultFileList: [...fileList],
};
const props2 = {
action: '//jsonplaceholder.typicode.com/posts/',
listType: 'picture',
defaultFileList: [...fileList],
className: 'upload-list-inline',
};
ReactDOM.render(
<div>
<Upload {...props}>
<Button>
<Icon type="upload" /> upload
</Button>
</Upload>
<br />
<br />
<Upload {...props2}>
<Button>
<Icon type="upload" /> upload
</Button>
</Upload>
</div>
, mountNode);
/* tile uploaded pictures */
.upload-list-inline .ant-upload-list-item {
float: left;
width: 200px;
margin-right: 8px;
}
.upload-list-inline .ant-upload-animate-enter {
animation-name: uploadAnimateInlineIn;
}
.upload-list-inline .ant-upload-animate-leave {
animation-name: uploadAnimateInlineOut;
}
Click to upload user’s avatar, and validate size and format of picture with beforeUpload
.
The return value of function
beforeUpload
can be a Promise to check asynchronously. demo
import { Upload, Icon, message } from 'antd';
function getBase64(img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
function beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
if (!isJPG) {
message.error('You can only upload JPG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJPG && isLt2M;
}
class Avatar extends React.Component {
state = {
loading: false,
};
handleChange = (info) => {
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
getBase64(info.file.originFileObj, imageUrl => this.setState({
imageUrl,
loading: false,
}));
}
}
render() {
const uploadButton = (
<div>
<Icon type={this.state.loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">Upload</div>
</div>
);
const imageUrl = this.state.imageUrl;
return (
<Upload
name="avatar"
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action="//jsonplaceholder.typicode.com/posts/"
beforeUpload={beforeUpload}
onChange={this.handleChange}
>
{imageUrl ? <img src={imageUrl} alt="" /> : uploadButton}
</Upload>
);
}
}
ReactDOM.render(<Avatar />, mountNode);
.avatar-uploader > .ant-upload {
width: 128px;
height: 128px;
}
After users upload picture, the thumbnail will be shown in list. The upload button will disappear when count meets limitation.
import { Upload, Icon, Modal } from 'antd';
class PicturesWall extends React.Component {
state = {
previewVisible: false,
previewImage: '',
fileList: [{
uid: -1,
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}],
};
handleCancel = () => this.setState({ previewVisible: false })
handlePreview = (file) => {
this.setState({
previewImage: file.url || file.thumbUrl,
previewVisible: true,
});
}
handleChange = ({ fileList }) => this.setState({ fileList })
render() {
const { previewVisible, previewImage, fileList } = this.state;
const uploadButton = (
<div>
<Icon type="plus" />
<div className="ant-upload-text">Upload</div>
</div>
);
return (
<div className="clearfix">
<Upload
action="//jsonplaceholder.typicode.com/posts/"
listType="picture-card"
fileList={fileList}
onPreview={this.handlePreview}
onChange={this.handleChange}
>
{fileList.length >= 3 ? null : uploadButton}
</Upload>
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</div>
);
}
}
ReactDOM.render(<PicturesWall />, mountNode);
/* you can make up upload button and sample style by using stylesheets */
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
You can drag files to a specific area, to upload. Alternatively, you can also upload by selecting.
We can upload serveral files at once in modern browsers by giving the input the multiple
attribute.
import { Upload, Icon, message } from 'antd';
const Dragger = Upload.Dragger;
const props = {
name: 'file',
multiple: true,
action: '//jsonplaceholder.typicode.com/posts/',
onChange(info) {
const status = info.file.status;
if (status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`);
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
};
ReactDOM.render(
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
</Dragger>
, mountNode);
Upload files manually after beforeUpload
returns false
.
import { Upload, Button, Icon, message } from 'antd';
import reqwest from 'reqwest';
class Demo extends React.Component {
state = {
fileList: [],
uploading: false,
}
handleUpload = () => {
const { fileList } = this.state;
const formData = new FormData();
fileList.forEach((file) => {
formData.append('files[]', file);
});
this.setState({
uploading: true,
});
// You can use any AJAX library you like
reqwest({
url: '//jsonplaceholder.typicode.com/posts/',
method: 'post',
processData: false,
data: formData,
success: () => {
this.setState({
fileList: [],
uploading: false,
});
message.success('upload successfully.');
},
error: () => {
this.setState({
uploading: false,
});
message.error('upload failed.');
},
});
}
render() {
const { uploading } = this.state;
const props = {
action: '//jsonplaceholder.typicode.com/posts/',
onRemove: (file) => {
this.setState(({ fileList }) => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
};
});
},
beforeUpload: (file) => {
this.setState(({ fileList }) => ({
fileList: [...fileList, file],
}));
return false;
},
fileList: this.state.fileList,
};
return (
<div>
<Upload {...props}>
<Button>
<Icon type="upload" /> Select File
</Button>
</Upload>
<Button
className="upload-demo-start"
type="primary"
onClick={this.handleUpload}
disabled={this.state.fileList.length === 0}
loading={uploading}
>
{uploading ? 'Uploading' : 'Start Upload' }
</Button>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.upload-demo-start {
margin-top: 16px;
}
Improve Article
Save Article
Improve Article
Save Article
Ant Design Library has this component pre-built, and it is very easy to integrate as well. Upload Component is used for uploading files by selecting or dragging. We can use the following approach in ReactJS to use the Ant Design Upload Component.
Upload Props:
- accept: It is used to denote the file types that can be accepted.
- action: It is used to denote the Uploading URL.
- beforeUpload: It is a Hook function that will be executed before uploading.
- customRequest: It is used to override the default xhr behavior.
- data: It is used for uploading extra params or function which can return uploading extra params.
- defaultFileList: It is used to denote the Default list of files that have been uploaded.
- directory: It is used to support upload the whole directory.
- disabled: It is used to disable the upload button.
- fileList: It is used to denote the list of files that have been uploaded.
- headers: It is used to set request headers.
- iconRender: It is used for the custom show icon.
- isImageUrl: It is used to customize if render <img /> in thumbnail.
- itemRender: It is used for the custom item of uploadList.
- listType: It is the Built-in stylesheets, support for text, picture, or picture-card type.
- maxCount: It is used to limit the number of uploaded files.
- method: It is the HTTP method of upload request.
- multiple: It is used to indicate whether to support selected multiple files.
- name: It is used to denote the name of the uploading file.
- openFileDialogOnClick: It is used to open the file dialog on click of it.
- previewFile: It is used for the customized preview file logic.
- progress: It is used for the Custom progress bar.
- showUploadList: It is used to indicate whether to show the default upload list.
- withCredentials: It is the Ajax upload with cookie sent.
- onChange: It is a callback function that is triggered when the uploading state is changing.
- onDrop: It is a callback function that is triggered when files are dragged and dropped into the upload area.
- onDownload: It is the method to download the file when it is clicked.
- onPreview: It is a callback function that is triggered when a file link or preview icon is clicked.
- onRemove: It is a callback function that is triggered when the removing file button is clicked.
UploadFile Props:
- name: It is used to denote the file name.
- percent: It is used to denote the upload progress percent.
- status: It is used to denote the upload status.
- thumbUrl: It is used to pass the thumb image URL.
- uid: It is the unique ID, and it will be generated automatically when not provided.
- url: It is used to denote the download URL.
Creating React Application And Installing Module:
-
Step 1: Create a React application using the following command:
npx create-react-app foldername
-
Step 2: After creating your project folder i.e. foldername, move to it using the following command:
cd foldername
-
Step 3: After creating the ReactJS application, Install the required module using the following command:
npm install antd
Project Structure: It will look like the following.
Project Structure
Example: Now write down the following code in the App.js file. Here, App is our default component where we have written our code.
App.js
import React from
'react'
import
"antd/dist/antd.css"
;
import { Upload, message, Button } from
'antd'
;
const props = {
headers: {
authorization:
'authorization-text'
,
},
name:
'file'
,
};
export
default
function
App() {
return
(
<div style={{
display:
'block'
, width: 700, padding: 30
}}>
<h4>ReactJS Ant-Design Upload Component</h4>
<>
<Upload {...props}
onChange={(response) => {
if
(response.file.status !==
'uploading'
) {
console.log(response.file, response.fileList);
}
if
(response.file.status ===
'done'
) {
message.success(`${response.file.name}
file uploaded successfully`);
}
else
if
(response.file.status ===
'error'
) {
message.error(`${response.file.name}
file upload failed.`);
}
}}
>
<Button>Upload File</Button>
</Upload>
</>
</div>
);
}
Step to Run Application: Run the application using the following command from the root directory of the project:
npm start
Output: Now open your browser and go to http://localhost:3000/, you will see the following output:
Reference: https://ant.design/components/upload/
<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:
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>