当按钮类型为 "submit" 时,useState 挂钩不会触发

When button type is "submit", useState hook doesn't fire

如标题所示,我有一个 form 组件,它有一个 onSubmit 属性集来处理表单提交。通常在我的应用程序中,当用户提交表单时,会启动一个很长的 运行 过程。在那段时间里,我想显示一个 spinner/disable 按钮来阻止用户在查询加载时提交表单。

但是,当提交 button 类型为 submit 时,来自 useState 挂钩(甚至是 Redux 调度程序)的任何状态设置器似乎都不会更改状态。

我看到调用提交处理程序时组件为 re-rendered,即使我使用了 event.preventDefault()event.stopPropagation()。我怎样才能防止这种 re-render 发生并扰乱状态?

我的代码如下,I have create a minimum reproduction in a Code Sandbox here. 我使用了一个 setTimeout 函数来模拟实际发生的 API 调用。

import React, {
  ChangeEvent,
  FormEvent,
  FunctionComponent,
  useRef,
  useState
} from "react";
import { Button, Form, Modal, Spinner } from "react-bootstrap";

const SimpleForm: FunctionComponent = () => {
  // form state
  const [name, setName] = useState("TestName");
  const [age, setAge] = useState("10");

  // form state helpers
  const [submitLoading, setSubmitLoading] = useState(false);

  const formRef = useRef<HTMLFormElement | null>(null);

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    event.stopPropagation();
    const form = event.currentTarget;
    if (form.checkValidity()) {
      setSubmitLoading(true);
      console.log("valid form");
      setTimeout(() => console.log("blah"), 5000);
      setSubmitLoading(false);
    }
  };

  return (
    <Modal centered size="lg" show={true}>
      <Form ref={formRef} onSubmit={handleSubmit}>
        <Modal.Header closeButton>
          <Modal.Title>Simple Form</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <Form.Group className="mb-3" controlId="instanceName">
            <Form.Label as="h6">Name</Form.Label>
            <Form.Control
              required
              type="text"
              placeholder="Instance name here"
              value={name}
              spellCheck="false"
              autoComplete="off"
              onChange={(e) => {
                setName(e.target.value);
              }}
            />
          </Form.Group>
          <Form.Group className="mb-3" controlId="diskSize">
            <Form.Label as="h6">Age</Form.Label>
            <Form.Control
              type="number"
              min="10"
              max="1000"
              value={age}
              onChange={(e: ChangeEvent<HTMLInputElement>) =>
                setAge(e.target.value)
              }
            />
          </Form.Group>
        </Modal.Body>
        <Modal.Footer>
          <Button variant="secondary">Close</Button>
          <Button
            type="submit"
            variant="success"
            style={{
              padding: submitLoading ? "0.375rem 3.875rem" : "0.375rem 0.8rem"
            }}
          >
            {submitLoading ? (
              <Spinner size="sm" animation="border" />
            ) : (
              "Submit Form"
            )}
          </Button>
        </Modal.Footer>
      </Form>
    </Modal>
  );
};

我试过用一个闭包替换函数的内部,有和没有以前的状态 (() => true / (prevState) => !prevState)。我尝试用 useRef 挂钩替换 useState 挂钩,并在事件处理函数中手动设置 ref。我也将状态转移到 Redux 中,但这似乎也没有做任何事情。我还尝试向 button 添加一个 onClick 道具,里面有一个闭包,但似乎也没有任何作用。


如有任何帮助,我们将不胜感激!

一个问题是setTimeout在这里什么都不做,因为它作为setSubmitLoading函数在5000ms之后运行,js基本上“忽略”这里的setTimeout,除非你使用await,这使得它等待函数完成(使用等待实际的 api 请求,它不适用于 setTimeout)

这意味着 React 立即更新状态并没有真正改变任何东西

这表明您的 onSubmit 函数可能没有正确使用 await,或者 .then Promise 回调

asyncFunction().then(() => setState(false))

您在将加载状态设置为 true 后立即将其设置为 false,因此您看不到任何变化。如果您将 setSubmitLoading(false) 移到 setTimeOut 中,更改将反映给您,就像我在沙盒中所做的那样。

const handleSubmit = () => {
    console.log(`submitLoading in handler: ${submitLoading}`);
    setSubmitLoading(true);
    console.log("valid form");
    setTimeout(() => setSubmitLoading(false), 5000);
  };