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

Time:11-13

As the title suggests, I have a form component that has an onSubmit prop set to handle form submissions. Typically in my application, when a user submits a form, a long running process starts. During that time I want to display a spinner/disable the button to stop the user from submitting the form while the query loads.

However, when the submit button type is submit, any state setters that come from the useState hook (or even a Redux dispatcher) don't seem to change the state.

I see that the component is re-rendered when the submit handler is invoked, even though I used event.preventDefault() and event.stopPropagation(). How can I prevent this re-render from occurring and messing with the state?

My code is below, and I have create a minimum reproduction in a Code Sandbox here. I utilized a setTimeout function to mimic the API call that actually happens.

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

I have tried replacing the inside of the function with a closure with and without the previous state (() => true / (prevState) => !prevState). I tried replacing the useState hook with a useRef hook, and setting the ref manually inside the event handling function. I also moved the state into Redux, and that didn't seem to do anything either. I also tried adding an onClick prop to the button with a closure inside, and that seems to do nothing either.


Any help would be much appreciated!

CodePudding user response:

You're setting the loading state to false immediately after setting it to true, thus you don't see any changes. If you move the setSubmitLoading(false) INSIDE the setTimeOut, the changes will reflect for you, as it did for me on the sandbox.

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

CodePudding user response:

One problem would be that the setTimeout does nothing here, as it runs 5000ms later as the setSubmitLoading function, js basically "ignores" the setTimeout here unless you use await, which makes it wait for the function to complete (using await for the actual api request, it doesnt work on setTimeout)

which means React updates the state instantly not really changing anything

this suggests that maybe your onSubmit function doesnt use await correctly, or .then Promise callbacks

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