如何防止在单击事件上触发两个相同的 useState 挂钩

Posted

技术标签:

【中文标题】如何防止在单击事件上触发两个相同的 useState 挂钩【英文标题】:how to prevent two same useState hooks from being triggered on one click event 【发布时间】:2020-08-31 04:47:48 【问题描述】:

我有 2 个具有相似逻辑的组件,它们都有条件地显示一条消息,基于来自 useState 钩子的本地状态和来自 redux 状态的两个道具。但是,当我单击 ConnectedPasswordChange 按钮时会出现问题 - 我看到两条消息,一条来自它自己的组件,一条来自 ProfileForm。知道如何摆脱它吗?和事件冒泡有关系吗?

const ConnectedPasswordChange = (): Node => 
  const [submitted, setSubmitted] = useState(false);

  const isUpdating = useSelector(state => state.user.isUpdating);
  const updateError = useSelector(state => state.user.updateError);

  const handleSubmit = () => 
    const  currentPassword, newPassword  = passwordDetails;
    dispatch(passwordUpdateRequested(currentPassword, newPassword));
    setSubmitted(true);
    setTimeout(() => setSubmitted(false), 5000);
  ;

  return (
    <Form onSubmit=handleSubmit>
      <PasswordChange passwordDetails=passwordDetails onPasswordChange=setPasswordDetails />
      <div style= textAlign: "center" >
        <Form.Button secondary>Button</Form.Button>
      </div>
      submitted && !isUpdating && !updateError && <Message positive content="Success" />
    </Form>
  );
;

在使用相同逻辑的 ProfileForm 组件中使用 ConnectedPasswordChange

const ProfileForm = ( id, user: initialUser, locations, isUpdating, updateError, onSubmit : Props): Node => 

  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = () => 
    onSubmit(id);
    setSubmitted(true);
    setTimeout(() => setSubmitted(false), 5000);
  ;

  return (
    <Segment className="my-profile-main-container">
      <Title />
      <Divider />
      <Form onSubmit=handleSubmit>
        <PersonalDetails personalDetails=user onPersonalDetailsChange=handleDetailsChange />
        <Divider />
        <BankDetails bankDetails=user onBankDetailsChange=handleDetailsChange />
        <div style= textAlign: "center" >
          <Form.Button secondary>Button</Form.Button>
        </div>
        submitted && !isUpdating && !updateError && <Message positive content=t("updateSucceeded") />
        <Divider />
        <ConnectedPasswordChange />
      </Form>
    </Segment>
  );
;

【问题讨论】:

尝试在 ConnectedPasswordChange 内的 handleSubmit 函数上使用 e.stopPropagation() 【参考方案1】:

嵌套表单并不常见,所以是的,如果您提交内部表单,它会出现,它也会冒泡并在外部表单组件上触发提交事件。为防止这种情况,您可以使用Event.prototype.stopPropagation():

const ProfileForm = () => 
  const tableRef = React.useRef();
  const handleSubmit = e => 
    e.preventDefault();
    tableRef.current.insertRow().append('ProfileForm submitted');
  ;
  
  return (
    <form onSubmit=handleSubmit>
      <fieldset>
        <legend>ProfileForm</legend>
        <ConnectedPasswordChange />
        <input type="submit" />
        <table ref=tableRef />
      </fieldset>
    </form>
  );
;

const ConnectedPasswordChange = () => 
  const tableRef = React.useRef();
  const handleSubmit = e => 
    e.preventDefault();
    e.stopPropagation(); // important! without this, ProfileForm will submit as well
    tableRef.current.insertRow().append('ConnectedPasswordChange submitted');
  ;
  
  return (
    <form onSubmit=handleSubmit>
      <fieldset>
        <legend>ConnectedPasswordChange</legend>
        <input type="submit" />
        <table ref=tableRef />
      </fieldset>
    </form>
  );
;

ReactDOM.render(<ProfileForm />, document.querySelector('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

<main></main>

请注意,生成的 html 无效,因为 &lt;form&gt; 元素 are not allowed to contain other &lt;form&gt; elements。如果您尝试使用像 Next.js 这样的服务器端 React 框架静态呈现此 HTML,您可能会发现文档可能不会像您预期的那样结束:

// the inner <form> was stripped from the markup
console.log(document.getElementById('ConnectedPasswordChange'));
console.log(document.querySelector('main').outerHTML);
<main>
  <form id="ProfileForm">
    <fieldset>
      <legend>ProfileForm</legend>
      <form id="ConnectedPasswordChange">
        <fieldset>
          <legend>ConnectedPasswordChange</legend>
          <input type="submit" />
          <table></table>
        </fieldset>
      </form>
      <input type="submit" />
      <table></table>
    </fieldset>
  </form>
</main>

【讨论】:

以上是关于如何防止在单击事件上触发两个相同的 useState 挂钩的主要内容,如果未能解决你的问题,请参考以下文章

如何防止默认事件触发但仍允许事件冒泡

防止在 Jquery 上触发父事件

当鼠标仍然按下时,如何防止在 Winforms Listview 中立即触发 MouseUp 事件?

如何防止指定任务触发

React,如何防止在拖动后执行单击事件?

防止拖动时触发点击事件[重复]