Компонент загрузки не работает должным образом, если поставляется со списком файлов по умолчанию - PullRequest
5 голосов
/ 10 апреля 2019

Я написал небольшой компонент, основанный на antd upload , с помощью которого пользователь мог загрузить несколько файлов на сервер. Я потратил много времени на отладку, но не могу понять некоторые из его поведений. Компонент выглядит следующим образом:

enter image description here

Есть 2 проблемы, с которыми я сталкиваюсь:

  • Всякий раз, когда компонент получает prefil, который содержит файлы, уже загруженные на сервер, я не могу добавить новые файлы. Как я пытаюсь загрузить новый файл после нажатия Add Another, который выглядит следующим образом

enter image description here

компонент возвращается в состояние по умолчанию, в котором изначально было 2 файла. Я просто не могу понять, как я мог справиться с этим.

  • Когда я пытаюсь удалить один из файлов по умолчанию, нажимая значок закрытия, он появляется снова после нажатия Add another.

Я знаю где-то, я не могу правильно управлять состоянием компонента, но я просто не могу понять сам. Вот код компонента, написанный с использованием машинописи.

    import { Button, Icon, Form, Input, Upload, message } from "antd";

    export type DefaultFileList = {
        uid: number | string;
        name: string;
        status?: string;
        url: string;
        fileLabel: string;
    };

    type state = {
        uploadFieldIdContainer: number[];
        mocAddErrorDescription?: string;
        uploadMap: { [index: number]: UploadFile[] };
        defaultMap: {
            [index: number]: {
            default: DefaultFileList[];
            fileLabel: string;
            };
        };
    };

    type oprops = {
        prefil: DefaultFileList[];
        buttonLabel?: string;
        type: string;
    };

    export default class DocumentUploader extends Component<
    FormComponentProps & oprops,
    state
    > {
        private maxUploadPerButton: number;

        constructor(props) {
            super(props);

            this.maxUploadPerButton = 1;

            const dMap = this.prepareDefaultFileMap();

            this.state = {
            uploadFieldIdContainer: this.getTotalDefaultDocuments(),
            uploadMap: {},
            defaultMap: dMap
            };
            this.addUploadFormField = this.addUploadFormField.bind(this);
            this.removeUploadField = this.removeUploadField.bind(this);
        }

        getTotalDefaultDocuments() {
            if (this.props.prefil && Array.isArray(this.props.prefil)) {
            return Array.from({ length: this.props.prefil.length }, (_, k) => k + 1);
            } else {
            return [];
            }
        }

        prepareDefaultFileMap() {
            if (this.props.prefil && this.props.prefil.length == 0) {
            return {};
            } else {
            const dMap = {};
            for (let i = 0; i < this.props.prefil.length; i++) {
                const p = this.props.prefil[i];
                const flabel = p.fileLabel;
                //delete p.fileLabel;
                dMap[i + 1] = {
                default: [p],
                fileLabel: flabel
                };
            }

            return dMap;
            }
        }

        async componentDidMount() {}

        componentWillReceiveProps(nextProps: FormComponentProps & oprops) {
            if (this.props.prefil.length > 0) {
            this.setState({
                uploadFieldIdContainer: this.getTotalDefaultDocuments(),
                defaultMap: this.prepareDefaultFileMap()
            });
            }
        }

        removeUploadField(key: number, event: React.MouseEvent<HTMLElement>) {
            event.preventDefault();

            /**
            * @see https://ant.design/components/form/?locale=en-US#components-form-demo-dynamic-form-item
            */

            this.setState(prevState => ({
            uploadFieldIdContainer: prevState.uploadFieldIdContainer.filter(
                field => field !== key
            )
            }));
        }

        getUploadFileProps(key: number): { [index: string]: any } {
            const _this = this;
            const { defaultMap } = this.state;

            const fileList = this.state.uploadMap[key] || [];
            const defaultFileList = (defaultMap[key] && defaultMap[key].default) || [];

            const props = {
            name: "file",
            action: getDocumentStoreUploadApi(),
            headers: HttpClient.requestConfig(),
            onPreview: (file: { [index: string]: any }) => {
                getFileFromDocumentStore(file.url, file.name);
            },
            beforeUpload(file: File, fileList: File[]) {
                if (file.type.match(/image/gi)) {
                return false;
                } else {
                return true;
                }
            },
            multiple: false,
            onChange(info: { [index: string]: any }) {
                console.log("changed..");
                let fileList = info.fileList;
                // 1. Limit the number of uploaded files
                // Only to show 1 recent uploaded file, and old ones will be replaced by the new
                fileList = fileList.slice(-1 * _this.maxUploadPerButton);

                // 2. Read from response and show file link
                fileList = fileList.map((file: { [index: string]: any }) => {
                if (file.response) {
                    // Component will show file.url as link
                    file.url = file.response.url;
                }
                return file;
                });

                const { uploadMap } = _this.state;
                Object.assign(uploadMap, { [key]: fileList });
                _this.setState({
                uploadMap
                });

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

            if (fileList.length > 0) {
            Object.assign(props, { fileList });
            } else if (defaultFileList.length > 0) {
            Object.assign(props, { defaultFileList });
            }

            return props;
        }

        getUploadField(key: number) {
            const { getFieldDecorator } = this.props.form;

            const { defaultMap } = this.state;
            const documentLabel = (defaultMap[key] && defaultMap[key].fileLabel) || "";

            return (
            <div className="d-flex justify-content-between">
                <div className="inline-block w-55">
                <FormItem label="Select File">
                    {getFieldDecorator(`selected_file_${this.props.type}[${key}]`, {
                    rules: [
                        {
                        required: "undefined" === typeof defaultMap[key],
                        message: "Please select the file to upload"
                        }
                    ]
                    })(
                    // <input type="file" id="input">
                    <Upload {...this.getUploadFileProps(key)}>
                        <Button disabled={false}>
                        <Icon type="upload" /> Click to Upload
                        </Button>
                    </Upload>
                    )}
                </FormItem>
                </div>

                <div className="inline-block w-45">
                <FormItem label="File Label">
                    {getFieldDecorator(
                    `selected_file_label_${this.props.type}[${key}]`,
                    {
                        rules: [
                        {
                            required: true,
                            message: "Please input the file label"
                        }
                        ],
                        initialValue: documentLabel
                    }
                    )(<Input type="text" />)}
                </FormItem>
                </div>
                <div className="inline-block pointer d-flex align-items-center">
                <span>
                    <Icon
                    type="close"
                    onClick={this.removeUploadField.bind(this, key)}
                    />
                </span>
                </div>
            </div>
            );
        }

        addUploadFormField(event: React.MouseEvent<HTMLElement>) {
            event.preventDefault();

            const { uploadFieldIdContainer } = this.state;

            // We only keep inside the state an array of number
            // each one of them represent a section of fields.
            const lastFieldId =
            uploadFieldIdContainer[uploadFieldIdContainer.length - 1] || 0;
            const nextFieldId = lastFieldId + 1;

            this.setState({
            uploadFieldIdContainer: uploadFieldIdContainer.concat(nextFieldId)
            });
        }

        getMainUploadButton() {
            return (
            <div className="d-flex w-100 mt-3">
                <Button
                type="primary"
                ghost={true}
                className="w-100 letter-spacing-1"
                onClick={this.addUploadFormField}
                >
                <Icon type="plus-circle" />
                {this.props.buttonLabel || "Select File(s) To Upload"}
                </Button>
            </div>
            );
        }

        getUploadFieldFooter() {
            return (
            <div className="d-flex justify-content-between small">
                <div className="inline-block">
                <Button
                    type="primary"
                    shape="circle"
                    icon="plus"
                    ghost={true}
                    size="small"
                    className="d-font mr-1"
                    onClick={this.addUploadFormField}
                />
                <div
                    className="text-primary pointer d-font inline-block letter-spacing-1 mt-1"
                    onClick={this.addUploadFormField}
                >
                    Add another&nbsp;
                </div>
                </div>
            </div>
            );
        }

        render() {
            const { uploadFieldIdContainer } = this.state;
            const mocButton = this.getMainUploadButton();

            const toRender =
            uploadFieldIdContainer.length > 0 ? (
                <div>
                <div className="w-100 p-2 gray-background br-25">
                    {uploadFieldIdContainer.map(fieldIndex => (
                    <div key={fieldIndex}>{this.getUploadField(fieldIndex)}</div>
                    ))}
                    {this.getUploadFieldFooter()}
                </div>
                </div>
            ) : (
                mocButton
            );

            return toRender;
        }
    }

render - это основной метод, который рендерит все поля ввода. Вышеуказанный компонент используется следующим образом:

      <DocumentUploader
        form={this.props.form}
        prefil={[{
            uid: "somehash",
            name: "name",
            url: "url",
            fileLabel: "label"
        }]}
        type="test"
      />

Я должен повторить, что проблема возникает только при инициализации компонента с файлами, уже загруженными на сервер, и прекрасно работает, при повторной попытке с компонентом, т.е. при первой загрузке.

1 Ответ

3 голосов
/ 10 апреля 2019

Если я правильно понимаю ваш код, я думаю, что this.props.prefil содержит файлы, загруженные на сервер.Если он правильный, вам нужно изменить код componentWillReceiveProps, чтобы он запускался только один раз, как указано ниже.

Сначала вы можете установить начальное состояние как:

this.state = {updateFlag: true};

А потом в componentWillReceiveProps как:

componentWillReceiveProps(nextProps: FormComponentProps & oprops) {
    if (this.props.prefil.length > 0 && this.state.updateFlag) {
        this.setState({
            uploadFieldIdContainer: this.getTotalDefaultDocuments(),
            defaultMap: this.prepareDefaultFileMap(),
            updateFlag: false,
        });
    }
}
...