为什么嵌套的React modal会自动触发函数调用?

Why does nested React modal automatically trigger function call?

我正在使用 react-responsive-modal 但我也尝试将其换成 react-modal - 结果相同。我也在为所有表单使用 react-hook-form。

我有一个模式可以触发另一个。出现的第一个模式加载另一个组件 - 一个表单。嵌套在表单中的模态是另一种形式。当我打开第一个表格时,一切都很好。当我拉起嵌套模态时,在第一个模态中提交表单的功能会自动触发。这对我来说毫无意义,也不应该成为可能。

加载第一个模式的页面,其中还包含函数,传递给第一个表单,这是意外自动触发的表单。

import React, { useState, useContext } from "react";
import { Modal } from "react-responsive-modal";

import ContextButton from "../global/context_button";
import ProfileEdit from "./profile_edit";

const ProfileBodyHeader = props => {
    const [editModalOpen, setEditModalOpen] = useState(false);

    const onEditClick = () => {
        setEditModalOpen(true);
    };

    /** THIS gets triggered automatically when second modal is called up **/
    const onEditSave = async profileData => {
        let updateRes = await updateProfileCombined(profileData);
        if (!updateRes) {
            return;
        }
        setEditModalOpen(false);
    };

    const onEditCancel = () => {
        setEditModalOpen(false);
    };

    const onEditClose = () => {
        setEditModalOpen(false);
    };

    return (
        <div className="profile-body-header-container">
            <div className="profile-body-header-content">
                <div className="profile-body-header-icons">
                    <div>
                        <ContextButton icon="bytesize:edit" onClick={() => onEditClick()} />
                        <Modal open={editModalOpen} onClose={onEditClose} showCloseIcon={false} center>
                            <ProfileEdit onSave={onEditSave} onCancel={onEditCancel} />
                        </Modal>                    
                    </div>
                </div>
            </div>
        </div>
    );
};

导出默认 ProfileBodyHeader;

这是加载到模态...表单中的 ProfileEdit 组件:

import React from "react";
import { useForm } from "react-hook-form";

import Links from "./links";
import FormError from "../global/form_error";

const ProfileEdit = props => {
    const { onSave, onCancel, isPrimary } = props;

    let defaultValues = getProfileEditDefaults();

    const { register, setValue, handleSubmit, formState: { errors } } = useForm({ defaultValues });

    return (
        <div className="profile-edit-basic-container">
            <div className="profile-edit-basic-form">
                <form onSubmit={handleSubmit(onSave)}>
                    <input type="hidden" {...register("is_primary")} />
                    <div>
                        Profile Details
                    </div>
                    <div>
                        <label>First Name</label>
                        <input type="text" {...register("first_name")} />
                        {errors.first_name && <span className="validation">{errors.first_name}</span>}
                    </div>
                    <div>
                        <label>Last Name</label>
                        <input type="text" {...register("last_name")} />
                        {errors.last_name && <span className="validation">{errors.last_name}</span>}
                    </div>
                    
                    ...other fields
                    
                    <div>
                        Social Links
                    </div>
                    <div>
                        <Links isSocial={true} />
                    </div>
                    <div className="profile-edit-basic-buttons">
                        <button type="submit" className="form-button">Save</button>
                        <button type="button" className="form-button" onClick={() => onCancel()}>Cancel</button>
                    </div>
                    <FormError errors={errors} showDevMessage={true} />
                </form>
            </div>
        </div>
    );
};

export default ProfileEdit;

这是嵌套的链接组件。您可以看到有一个 LinksList 和一个 LinksEdit,我是按照与顶级表单相同的方式来做的。这是导致第一个表单自动提交的第二个模式的加载。

import React, { useState } from "react";
import { Modal } from "react-responsive-modal";

import ContextButton from "../global/context_button";
import LinksList from "./links_list";
import LinksEdit from "./links_edit";

const Links = props => {
    let { isSocial } = props;

    const [selectedEntity, setSelectedEntity] = useState(null);
    const [modalOpen, setModalOpen] = useState(false);

    const onEditClick = async () => {
        setModalOpen(true);
    };

    const onEditSave = async link => {
        let updateRes = await save(link);
        if (!updateRes) {
            return;
        }
        setSelectedEntity(null);
        setModalOpen(false);
    };

    const onEditCancel = async () => {
        setSelectedEntity(null);
        setModalOpen(false);
    };

    const onEditClose = async () => {
        setModalOpen(false);
    };

    const onEditDelete = async () => {
        let deleteRes = await remove(selectedEntity);
        if (!deleteRes) {
            return;
        }
        setSelectedEntity(null);
        setModalOpen(false);
    };

    return (
        <div id="links" className="links-container">
            <div className="links-header-container">
                <div className="links-header-body">
                    <div className="links-header-title">
                        Links
                    </div>
                </div>
                <div className="links-header-edit">
                    {/** WHETHER I click this... **/}
                    <ContextButton icon="carbon:add-alt" onClick={() => onEditClick()} />
                </div>
            </div>
            {/** ...OR I click the edit pencil from the list here, the onEditSave handler in ProfileBodyHeader is automatically triggered **/}
            <LinksList isSocial={isSocial} setSelectedEntity={setSelectedEntity} />
            <Modal open={(modalOpen || selectedEntity)} onClose={onEditClose} showCloseIcon={false} center>
                <LinksEdit isSocial={isSocial} selectedEntity={selectedEntity} onSave={onEditSave} onCancel={onEditCancel} onDelete={onEditDelete} />
            </Modal>                    
        </div>
    );
};

export default Links;

此链接组件也在嵌套模式之外使用。我想重用它 w/o 大量修改的宏伟计划可能不会发生。我不知道我是否有重叠的事件处理程序名称,或者是导致此问题的原因。我经历了将这些中的所有事件处理程序重命名为更具体的名称的练习,但它没有用。我找不到问题!

好吧,我找到了解决方法...我想。这很丑陋,但这是我所知道的最好的了。

当子模式打开时,我无法阻止父表单提交,但我能够通过测试来自 useForm 挂钩中的 formState 对象的 isDirty 来阻止提交,如下所示:

const { register, handleSubmit, formState: { errors, isDirty } } = useForm({ defaultValues });

...

const handleEditSubmit = async e => {
    e.preventDefault();
    if (!isDirty) return;
    await handleSubmit(onSave)(e);
};

如果有“正确的方法”来做到这一点,我很想知道它是什么!