39

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;
Luis Valencia
  • 32,619
  • 93
  • 286
  • 506
  • It would be useful to trim this example down to a [mcve], because at the moment there is a lot of irrelevant code to dig through. A minimal, unstyled form with a single file input would possibly be all you need to reproduce your issue. – Tom Fenech Jul 25 '18 at 08:56
  • no, thats not possible, the example needs all input controls and validations, otherwise it would just work as in the examples. – Luis Valencia Jul 25 '18 at 08:58

5 Answers5

81

TL;DR

Override <Upload/> default upload AJAX implementation with a simulated successful upload.

Solution Demo:
Edit antd upload component as file selector

Full Answer

It seems that you are trying to use andt's <Upload/> component simply as file selector, due to the fact that you append the file to formData by yourself. The reason for never hitting the point where status === "done" is because the file is not really uploaded to anywhere.
Thus, you don't need the file to be uploaded automatically like it should OOB.
All you need is that the onChange will send you the selected file and you can save it somewhere in the state tree.
<Upload/> renders another component (rc-upload) as its child which handles the actual AJAX upload. You can override this behaviour by passing a customRequest prop to <Upload/>(which will be passed to rc-upload component).

You can see here which options are passed to the request function. Here is an implementation of a dummy request function:

const dummyRequest = ({ file, onSuccess }) => {
  setTimeout(() => {
    onSuccess("ok");
  }, 0);
};

Then you can pass it to <Upload customRequest={dummyRequest}/>
onChange will still be triggered, but this time with the "done" status, since the dummy request function simulates a flow of successful upload.

Ramy Ben Aroya
  • 2,333
  • 14
  • 20
  • 4
    Thanks for the answer AND example - youre spoiling us – Amr Apr 17 '19 at 11:32
  • 1
    What if you do need to send the file data somewhere, you just want to hold off on the actual upload until the user hits the "submit" button at the bottom of the form as opposed to the upload button in the middle of the form? – maxwell Jan 23 '21 at 23:43
  • is the `setTimeout` really needed? – lanxion Nov 26 '21 at 10:56
  • @lanxion without the `setTimeout` the `onChange` will be triggered only once with status `"uploading"`. Probably the `` component relies somehow that the `customRequest`'s `onSuccess` is called sometime after the current runloop. – Ramy Ben Aroya Nov 28 '21 at 08:51
19

Extending to @Quincy's answer, you can also use shorthand on component something like this,

<Upload beforeUpload={() => false} />
sandiprb
  • 442
  • 7
  • 12
17

According to the official documentation Upload manually:

Upload files manually after beforeUpload returns false.

beforeUpload(file) {
    const isJPG = file.type === 'image/jpeg';
    if (!isJPG) {
      message.error('You can only upload JPG file!');
    }
    return false;
}
Quincy
  • 171
  • 1
  • 4
0

Antd: Simplest solution to upload the only jpeg or png file by using beforeUpload

<Upload
    fileList = {this.state.selectedLogo}
    customRequest = {customRequest}
    onChange = {this.onChangeLogo}
    showUploadList = {false}
    beforeUpload = {(file) => {
    const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
        if (!isJPG) {
            message.error('You can only upload JPG or PNG file!');
            return false;
        } else {
            return true;
        }
    }}
>
Farrukh Malik
  • 746
  • 9
  • 13
0

Doing something similar with Antd Dragger to upload csv's in React Native and was able to use Ramy Ben Aroya's answer to get it working:

const dummyRequest = async ({ file, onSuccess }) => {    
   setTimeout(() => {
      onSuccess("ok");
   }, 0);
 }


const fileProps = {
    name: 'file',
    multiple: true,
    customRequest: dummyRequest,
    onChange(info:any) {
      const { status } = info.file;
      if (status !== 'uploading') {
        console.log('uploading');
      }
      if (status === 'done') {
        getBase64(info.file.originFileObj, async (file: string) => {
          // handle data base or parse logic here using file
        });
       message.success(`${info.file.name} file uploaded successfully.`);
      } else if (status === 'error') {
        message.error(`${info.file.name} file upload failed.`);
      }
    },
    onDrop(e) {
      console.log('Dropped files', e.dataTransfer.files);
    },
  };


<Dragger {...fileProps} style={{ maxHeight: "180px" }}>
  <p className="ant-upload-drag-icon">
    <InboxOutlined />
  </p>
  <p className="ant-upload-text">Click or drag file to this area to upload</p>
  <p className="ant-upload-hint">
     Supported file types: .csv 
  </p>
</Dragger>
cormacncheese
  • 1,251
  • 1
  • 13
  • 27