reactjsformsantd

How to handle multiple submit buttons with Antd Forms?


Consider the following code:

import { Button, Form } from 'antd'

export function App() {
  const submitForm = (data: {submit: 'foo' | 'bar'}) => {
    console.log(data)
  }

  return (
    <Form onFinish={submitForm}>
      <Button htmlType="submit" name="submit" value="foo">Foo</Button>
      <Button htmlType="submit" name="submit" value="bar">Bar</Button>
    </Form>
  )
}

When clicking either button, I would expect data.submit in submitForm to be either foo or bar, but it is unset entirely; console.log returns {} instead. The ability to provide HTML buttons name and value is standard, and allows for forms where there are multiple submit buttons (like different actions), but for the same input data. Is there a way to obtain a similar behaviour with antd's Form?

I've seen it suggested to use useRef instead and capture it for each button's onClick event; but I was really hoping there would be a nicer solution.


Solution

  • I don't think you'll find any much more "elegant" solution to the useRef solution if you are set on using AntD forms and components.

    The problem is that you have no access to the underlying form element's onSubmit event object in submitForm and your form has no fields, and therefore no values to submit and pass to the submitForm/onFinish handler, which is why you see only an empty {} object logged.

    An (IMHO) as-trivial alternative I see is using a hidden "submit" field and the form instance directly to set its value.

    Example:

    export function MyForm() {
      // Create a form instance manually
      const [form] = Form.useForm();
    
      const submitForm = (data: { submit: "foo" | "bar" }) => {
        console.log(data);
      };
    
      return (
        <Form
          form={form} // pass form instance
          onFinish={submitForm}
        >
          {/* Hidden "submit" field */}
          <Form.Item name="submit" hidden></Form.Item>
          <Button
            htmlType="submit"
            onClick={() => {
              // Manually set "submit" field value to "foo"
              form.setFieldValue("submit", "foo");
            }}
          >
            Foo
          </Button>
          <Button
            htmlType="submit"
            onClick={() => {
              // Manually set "submit" field value to "boo"
              form.setFieldValue("submit", "bar");
            }}
          >
            Bar
          </Button>
        </Form>
      );
    }
    

    Another alternative I can think of is to assign an ID to the form and access the underlying form DOM element and add your own "submit" event listener and do whatever you need to do with the submit event object.

    Note however, that it generally is not advised to access DOMNodes directly in React if other means are available. In other words, this would be more of a "all other methods have been exhausted" type of solution.

    Example:

    useEffect(() => {
      const formEl = document.getElementById("myForm");
    
      const submitHandler = (e: SubmitEvent) => {
        // Do what you need with `e.submitter?.value`, the 
        // button element's `value` prop value.
      };
    
      if (formEl) {
        formEl.addEventListener("submit", submitHandler);
    
        return () => {
          formEl.removeEventListener("submit", submitHandler);
        };
      }
    }, []);
    
    ...
    
    return (
      <Form name="myForm" onFinish={submitForm}>
        <Button htmlType="submit" name="submit" value="foo">Foo</Button>
        <Button htmlType="submit" name="submit" value="bar">Bar</Button>
      </Form>
    )
    

    For example, you could save the value into a React Ref or state, or issue any other additional side-effect logic, though this isn't necessarily better than the useRef solution or the hidden field suggestions.