Sanjay Pandit
Sanjay Pandit

Reputation: 9

How to handle loading states for mutliple submit buttons in Next JS form using useFormStatus hook

I have a simple html form in Next JS 14.2.2 (app router) application. The form has two submit buttons, lets says Button1 and Button2. I am using useFormStatus hook from react-dom to handle the loading indicator in each of the submit buttons.

When I am submitting the form by clicking on Button1, the loading indicator starts for both the buttons. I want only specific button to show loading indicator which was clicked. How can we handle this ?

I tried using name parameter on each of the buttons but that information is not present in the formStatus variable available from useFormStatus hook

Upvotes: 0

Views: 497

Answers (2)

MarkC
MarkC

Reputation: 185

This has worked for me:

  1. Create a button component that I use for all submit buttons on the form. In the case of a login page this could be the login with user/password, signup or log in with Google etc.
  2. In the button component check for pending status on the form, but, and here is the important part, also track whether it was this button that was clicked.

So the code for the button would look like this:

export function SubmitButton({ children, pendingText, ...props }: Props) {
  const { pending } = useFormStatus();
  const [buttonClicked, setButtonClicked] = useState(false);

  return (
    <Button type="submit" aria-disabled={pending} disabled={pending} onClick={() => setButtonClicked(true)} {...props}>
      {pending && buttonClicked ? pendingText : children}
    </Button>
  );
}

Upvotes: 1

Tushar Shahi
Tushar Shahi

Reputation: 20626

There is no out of box solution for this. Hence there can be multiple ways to do this. One of them can be by having an extra state variable that helps let you know which was the button that was clicked.

Suppose below is your component UserNameForm which is wrapped by the form component in the parent.

You can add a state variable which holds the value of the latest clicked button. Based on that you can set the value of pending for each individual button. The only thing worth noting is that you will have to add extra onClick handlers to each button, which you probably do not have earlier so this adds up to some code.

export default function UsernameForm() {
  const [buttonClickState, setButtonClickState] = useState(null);
  const { pending, data } = useFormStatus();

  const updateButtonClickState = (buttonType) => {
    setButtonClickState(buttonType);
  };

  useEffect(() => {
    if (!pending) {
      setButtonClickState(null);
    }
  }, [pending]);

  return (
    <div>
      <h3>Request a Username: </h3>
      <input type="text" name="username" disabled={pending} />
      <button
        type="submit"
        disabled={buttonClickState === "submit" ? pending : false}
        onClick={() => {
          updateButtonClickState("submit");
        }}
      >
        Submit
      </button>
      <button
        type="submit"
        disabled={buttonClickState === "save" ? pending : false}
        onClick={() => {
          updateButtonClickState("save");
        }}
      >
        Save
      </button>
      <br />
      <p>{data ? `Requesting ${data?.get("username")}...` : ""}</p>
    </div>
  );
}

The whole functionality can also be moved to a hook like this:

const useButtonStatus = ({ pending }) => {
  const [buttonClickState, setButtonClickState] = useState(null);

  useEffect(() => {
    if (!pending) {
      setButtonClickState(null);
    }
  }, [pending]);

  const updateButtonClickState = (buttonType) => {
    setButtonClickState(buttonType);
  };

  return {
    updateButtonClickState,
    submitDisabled: buttonClickState === "submit" ? pending : false,
    saveDisabled: buttonClickState === "save" ? pending : false,
  };
};

And then it can be easily decided which button to show as disabled:

      <button
        type="submit"
        disabled={submitDisabled}
        onClick={() => {
          updateButtonClickState("submit");
        }}
      >
        Submit
      </button>
      <button
        type="submit"
        disabled={saveDisabled}
        onClick={() => {
          updateButtonClickState("save");
        }}
      >
        Save
      </button>

Example

Upvotes: 0

Related Questions