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

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

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

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

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

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

компонент возвращается к состоянию по умолчанию, в котором изначально было 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.info("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"
      />

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

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
5
0
1 987
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если я правильно понимаю ваш код, я думаю, что 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,
        });
    }
}

Другие вопросы по теме