Optional callbacks for handleSubmit #668
-
| I don't know if this is a common use case, but I sometimes have multiple buttons with a form that each have a different submit action. A simple change that I think accomplishes this is optional parameters for handleSubmit handleSubmit = async ( 
  onSubmit?: (props: {
      value: TFormData
      formApi: FormApi<TFormData, TFormValidator>
    }) => any | Promise<any>
  onSubmitInvalid?: (props: {
      value: TFormData
      formApi: FormApi<TFormData, TFormValidator>
    }) => void) {
onSubmit ??= this.options.onSubmit
onSubmitInvalid ??= this.options.onSubmitInvalid
// replace options handlers with local handlersThen I could call handleSubmit with different callbacks on the different buttons. | 
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 2 replies
-
| I don't think this logic should live in TanStack Form to handle multiple button presses. You can do this rather trivially otherwise with individual functions in your codebase outside of Form, right? 
 | 
Beta Was this translation helpful? Give feedback.
-
| Thanks for the response. This is how I would do it now. It's not too bad, but does feel a little odd/too much for something simple. But I could foresee issues with the async timing on it if I was not careful to only allow one option to handle at a time. Maybe there is a better way to do this and I am missing something. import type { FieldApi } from '@tanstack/react-form';
import { useForm } from '@tanstack/react-form';
import * as React from 'react';
import { createRoot } from 'react-dom/client';
function FieldInfo({ field }: { field: FieldApi<any, any, any, any> }) {
  return (
    <>
      {field.state.meta.touchedErrors ? (
        <em>{field.state.meta.touchedErrors}</em>
      ) : null}
      {field.state.meta.isValidating ? 'Validating...' : null}
    </>
  );
}
export default function App() {
  const form = useForm({
    defaultValues: {
      firstName: '',
      lastName: '',
    },
    onSubmit: async ({ value }) => {
      if (flag.current === 1) {
        submitToApi1({ value });
      }
      if (flag.current === 2) {
        submitToApi2({ value });
      }
    },
  });
  const flag = React.useRef(1);
  function submitToApi1({ value }) {
    alert('Api 1');
  }
  function submitToApi2({ value }) {
    alert('Api 2');
  }
  return (
    <div>
      <h1>Simple Form Example</h1>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          e.stopPropagation();
          // form.handleSubmit();
        }}
      >
        <div>
          {/* A type-safe field component*/}
          <form.Field
            name="firstName"
            validators={{
              onChange: ({ value }) =>
                !value
                  ? 'A first name is required'
                  : value.length < 3
                  ? 'First name must be at least 3 characters'
                  : undefined,
              onChangeAsyncDebounceMs: 500,
              onChangeAsync: async ({ value }) => {
                await new Promise((resolve) => setTimeout(resolve, 1000));
                return (
                  value.includes('error') && 'No "error" allowed in first name'
                );
              },
            }}
            children={(field) => {
              // Avoid hasty abstractions. Render props are great!
              return (
                <>
                  <label htmlFor={field.name}>First Name:</label>
                  <input
                    id={field.name}
                    name={field.name}
                    value={field.state.value}
                    onBlur={field.handleBlur}
                    onChange={(e) => field.handleChange(e.target.value)}
                  />
                  <FieldInfo field={field} />
                </>
              );
            }}
          />
        </div>
        <div>
          <form.Field
            name="lastName"
            children={(field) => (
              <>
                <label htmlFor={field.name}>Last Name:</label>
                <input
                  id={field.name}
                  name={field.name}
                  value={field.state.value}
                  onBlur={field.handleBlur}
                  onChange={(e) => field.handleChange(e.target.value)}
                />
                <FieldInfo field={field} />
              </>
            )}
          />
        </div>
        <button
          onClick={() => {
            flag.current = 1;
            form.handleSubmit();
          }}
        >
          Submit to Api 1
        </button>
        <button
          onClick={() => {
            flag.current = 2;
            form.handleSubmit();
          }}
        >
          Submit to Api 2
        </button>
      </form>
    </div>
  );
}
const rootElement = document.getElementById('root')!;
createRoot(rootElement).render(<App />);What I would like to be able to do.         <button
          onClick={() => {
            form.handleSubmit(submitToApi1);
          }}
        >
          Submit to Api 1
        </button>
        <button
          onClick={() => {
            form.handleSubmit(submitToApi2);
          }}
        >
          Submit to Api 2
        </button> | 
Beta Was this translation helpful? Give feedback.
-
| @crutchcorn we have a similar case and introducing a ref just for passing a parameter seems to be an anti-pattern. Could handleSubmit not just return the form values? That seems to align with the overall API design and allows for such a callback. | 
Beta Was this translation helpful? Give feedback.
I don't think this logic should live in TanStack Form to handle multiple button presses. You can do this rather trivially otherwise with individual functions in your codebase outside of Form, right?