javascriptreactjsform-submit

onSubmit event is not called after onClick


This is test code. When I click update button, it calls the updateItem function and then handleSubmit function, but when I click delete button, it calls only removeItem function. Both buttons are type="submit" button types.

import React, { useState } from "react";

function MyForm() {
  const [items, setItems] = useState([
    { id: 1, name: "item1" },
    { id: 2, name: "item2" },
    { id: 3, name: "item3" },
  ]);

  const removeItem = (id: number) => {
    setItems(items.filter((item) => item.id !== id));
  };

  const updateItem = (id: number) => {
    setItems(
      items.map((item) => {
        if (item.id === id) {
          return { ...item, name: "test" };
        } else {
          return item;
        }
      })
    );
  };

  const handleSubmit = (event: any) => {
    event.preventDefault();
    console.log("submit");
  };

  return (
    <form onSubmit={handleSubmit}>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            {item.name}
            <button type="submit" onClick={() => removeItem(item.id)}>
              Remove
            </button>
            <button type="submit" onClick={() => updateItem(item.id)}>
              Update
            </button>
          </li>
        ))}
      </ul>
    </form>
  );
}

export default MyForm;

I expect that delete button call onClick event and then submit event like update button.


Solution

  • Hey, first let's understand what is happening here.

    When you click Update, React runs your onClick (which only changes state), then the button remains mounted, so the browser continues to fire the form’s native submit event, which React catches in your handleSubmit. But when you click Remove, your onClick immediately unmounts that <li> (and its <button>), so the original submit action never makes it up to the <form>—the button you clicked is gone before the browser has a chance to complete the submit step. The fix is to avoid relying on the native click→submit sequence for Remove; instead, make Remove a plain button and explicitly tell the form to submit after you remove the item. The modern, standards-compliant way is to call the form’s [requestSubmit()][turn0search0] method, which behaves just like clicking a submit button (including running onSubmit), but won’t be interrupted by your unmount.

    The Fix: Use requestSubmit()

    function MyForm() {
      const [items, setItems] = useState([
        { id: 1, name: "item1" },
        { id: 2, name: "item2" },
        { id: 3, name: "item3" },
      ]);
    
      const removeItem = (id) => {
        setItems(items.filter(item => item.id !== id));
      };
    
      const handleSubmit = (event) => {
        event.preventDefault();
        console.log("submit");
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <ul>
            {items.map(item => (
              <li key={item.id}>
                {item.name}
    
                {/* UPDATE can stay submit-type */}
                <button
                  type="submit"
                  onClick={() => {
                    // ...update logic here
                  }}
                >
                  Update
                </button>
    
                {/* REMOVE as plain button, then requestSubmit */}
                <button
                  type="button"
                  onClick={e => {
                    // 1) remove it
                    removeItem(item.id);
                    // 2) then tell the form to submit
                    e.currentTarget.form.requestSubmit();
                  }}
                >
                  Remove
                </button>
              </li>
            ))}
          </ul>
        </form>
      );
    }
    

    This is the minimal, robust change that ensures Remove always does its work then triggers your form’s submit logic.