2

I'm using React 16.13 and Bootstrap 4. I have the following form container ...

const FormContainer = (props) => {
    ...
  const handleFormSubmit = (e) => {
    e.preventDefault();
    CoopService.save(coop, setErrors, function(data) {
      const result = data;
      history.push({
        pathname: "/" + result.id + "/people",
        state: { coop: result, message: "Success" },
      });
      window.scrollTo(0, 0);
    });
  };

  return (
    <div>
      <form className="container-fluid" onSubmit={handleFormSubmit}>
        <FormGroup controlId="formBasicText">
    ...
          {/* Web site of the cooperative */}
          <Button
            action={handleFormSubmit}
            type={"primary"}
            title={"Submit"}
            style={buttonStyle}
          />{" "}
          {/*Submit */}
        </FormGroup>
      </form>
    </div>
  );

Is there a standard way to disable the submit button to prevent a duplicate form submission? The catch is if there are errors in the form that come back from the server, I would like the button to be enabled again. Below is the "CoopService.save" I referenced above ...

...
  save(coop, setErrors, callback) {
    // Make a copy of the object in order to remove unneeded properties
    coop.addresses[0].raw = coop.addresses[0].formatted;
    const NC = JSON.parse(JSON.stringify(coop));
    delete NC.addresses[0].country;
    const body = JSON.stringify(NC);
    const url = coop.id
      ? REACT_APP_PROXY + "/coops/" + coop.id + "/"
      : REACT_APP_PROXY + "/coops/";
    const method = coop.id ? "PUT" : "POST";
    fetch(url, {
      method: method,
      body: body,
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
      },
    })
      .then((response) => {
        if (response.ok) {
          return response.json();
        } else {
          throw response;
        }
      })
      .then((data) => {
        callback(data);
      })
      .catch((err) => {
        console.log("errors ...");
        err.text().then((errorMessage) => {
          console.log(JSON.parse(errorMessage));
          setErrors(JSON.parse(errorMessage));
        });
      });
  }

Not sure if it's relevant, but here is my Button component. Willing to change it or the above to help with implementing a standard, out-of-the-box way to solve this.

import React from "react";
  
const Button = (props) => {
  return (
    <button
      style={props.style}
      className={
        props.type === "primary" ? "btn btn-primary" : "btn btn-secondary"
      }
      onClick={props.action}
    >
      {props.title}
    </button>
  );
};

export default Button;
Dave
  • 15,639
  • 133
  • 442
  • 830
  • 1
    Another solution could be to use a modal, since you can only have one open at a time. I use this method often for saving forms. – Greg M Aug 14 '20 at 14:27
  • Thanks @GregH. What you suggest seems like a custom solution, which I'm into, but I feel like I'm not the first person to want something like this -- do you think there are any out-of-the-box solutions or ways I can structure things to minimize the amount of custom code I have to write to accommodate something like this? – Dave Aug 14 '20 at 14:38
  • See https://stackoverflow.com/questions/55579068/how-to-prevent-a-double-click-in-reactjs The way to avoid multiple clicks in react on a button is using the disabled prop with a state. Change your save() function to async. At the top of save() set saving state to true (button will be disabled) and once you finish saving set the state to false (enable button). – Greg M Aug 14 '20 at 14:51
  • 1
    Thanks but that answer you linked to seems to be usign the "this.setState" paradigm whereas I'm using the newer "const [myItem setMyItem] = useState(...)" paradigm (not sure what the right way to refer to these two things as). – Dave Aug 16 '20 at 18:45
  • Re: `const [myItem setMyItem] = useState(...)`. It is very similar. Instead of `this.setState({state: newState})` just do `setMyItem(newState)` – Greg M Aug 17 '20 at 19:24
  • When you have some time research about class vs functional components. It will help a lot with understand react code since a lot of it is still in class components. – Greg M Aug 17 '20 at 19:26

2 Answers2

5

Greg already mentioned this link to show you how you can use component state to store whether or not the button is disabled.

However the latest version of React uses functional components with hooks rather than this.state or this.setState(...). Here's how you might go about that:

import { useState } from 'react';

const FormContainer = (props) => {
  ...
  const [buttonDisabled, setButtonDisabled] = useState(false);
  ...
  const handleFormSubmit = (e) => {
    setButtonDisabled(true); // <-- disable the button here
    e.preventDefault();
    CoopService.save(coop, (errors) => {setButtonDisabled(false); setErrors(errors);}, function(data) {
      const result = data;
      history.push({
        pathname: "/" + result.id + "/people",
        state: { coop: result, message: "Success" },
      });
      window.scrollTo(0, 0);
    });
  };

  return (
    ...
          <Button
            action={handleFormSubmit}
            disabled={buttonDisabled} // <-- pass in the boolean
            type={"primary"}
            title={"Submit"}
            style={buttonStyle}
          />
       ...
  );
const Button = (props) => {
  return (
    <button
      disabled={props.disabled} // <-- make sure to add it to your Button component
      style={props.style}
      className={
        props.type === "primary" ? "btn btn-primary" : "btn btn-secondary"
      }
      onClick={props.action}
    >
      {props.title}
    </button>
  );
};

I wrote some messy inline code to replace your setErrors function, but chances are you probably want to add setButtonDisabled(false); to the setErrors function wherever you originally defined it, rather than calling it from an anonymous function like I did; so keep that in mind.

More info about the useState hook can be found here. Let me know if that answers your question.

David McNamee
  • 403
  • 4
  • 10
0

As others have said, disabling the button is a perfect solution. But I dislike the button changing its visuals while submitting.

You should instead set the button's css property pointer-events: none which will turn off all events from being emitted. You can remove that property once submit is done or failed.

Mordechai
  • 15,437
  • 2
  • 41
  • 82