Skip to main content

useSubmit

The imperative version of <Form> that lets you submit a form from code instead of a user interaction.
This hook only works in Data and Framework modes.

Signature

function useSubmit(): SubmitFunction

interface SubmitFunction {
  (
    target: SubmitTarget,
    options?: SubmitOptions
  ): Promise<void>;
}

type SubmitTarget =
  | HTMLFormElement
  | FormData
  | Record<string, any>;

Parameters

None.

Returns

submit
SubmitFunction
A function that submits data to a route action.
target
SubmitTarget
required
The data to submit. Can be:
  • An HTMLFormElement - submits the form
  • A FormData object - submits the form data
  • A plain object - converted to FormData (or JSON if encType is "application/json")
options
SubmitOptions

Usage

Submit on form change

import { Form, useSubmit } from "react-router";

function SearchForm() {
  const submit = useSubmit();
  
  return (
    <Form onChange={(e) => submit(e.currentTarget)}>
      <input type="search" name="q" />
      <button type="submit">Search</button>
    </Form>
  );
}

Submit with button click

function Component() {
  const submit = useSubmit();
  
  return (
    <button
      onClick={() => {
        submit(
          { intent: "delete", id: "123" },
          { method: "post" }
        );
      }}
    >
      Delete
    </button>
  );
}

Submit FormData

function FileUpload() {
  const submit = useSubmit();
  
  return (
    <input
      type="file"
      onChange={(e) => {
        const formData = new FormData();
        formData.append("file", e.target.files[0]);
        
        submit(formData, {
          method: "post",
          encType: "multipart/form-data",
        });
      }}
    />
  );
}

Submit JSON

function SaveButton() {
  const submit = useSubmit();
  const data = { name: "John", email: "[email protected]" };
  
  return (
    <button
      onClick={() => {
        submit(
          data,
          {
            method: "post",
            encType: "application/json",
          }
        );
      }}
    >
      Save
    </button>
  );
}

Submit to different action

function MultiActionForm() {
  const submit = useSubmit();
  
  return (
    <Form>
      <input type="text" name="name" />
      
      <button
        type="button"
        onClick={(e) => {
          submit(e.currentTarget.form, {
            method: "post",
            action: "/save",
          });
        }}
      >
        Save
      </button>
      
      <button
        type="button"
        onClick={(e) => {
          submit(e.currentTarget.form, {
            method: "post",
            action: "/publish",
          });
        }}
      >
        Publish
      </button>
    </Form>
  );
}

Use as fetcher

function Component() {
  const submit = useSubmit();
  
  const deleteItem = (id: string) => {
    submit(
      { id },
      {
        method: "post",
        action: "/items/delete",
        navigate: false,  // Don't navigate, like a fetcher
        fetcherKey: `delete-${id}`,
      }
    );
  };
  
  return <button onClick={() => deleteItem("123")}>Delete</button>;
}

Common Patterns

import { useMemo } from "react";
import { useSubmit } from "react-router";
import debounce from "lodash.debounce";

function SearchInput() {
  const submit = useSubmit();
  
  const debouncedSubmit = useMemo(
    () =>
      debounce((form: HTMLFormElement) => {
        submit(form);
      }, 300),
    [submit]
  );
  
  return (
    <Form onChange={(e) => debouncedSubmit(e.currentTarget)}>
      <input type="search" name="q" />
    </Form>
  );
}

Auto-save form

function AutoSaveForm() {
  const submit = useSubmit();
  
  const handleChange = (e: React.FormEvent<HTMLFormElement>) => {
    submit(e.currentTarget, {
      method: "post",
      replace: true,  // Don't add history entries
    });
  };
  
  return (
    <Form onChange={handleChange}>
      <input type="text" name="title" />
      <textarea name="body" />
    </Form>
  );
}

Confirm before submit

function DeleteButton({ itemId }) {
  const submit = useSubmit();
  
  const handleDelete = () => {
    if (confirm("Are you sure?")) {
      submit(
        { id: itemId },
        { method: "post", action: "/delete" }
      );
    }
  };
  
  return <button onClick={handleDelete}>Delete</button>;
}

Submit on interval

function HeartbeatForm() {
  const submit = useSubmit();
  const formRef = useRef<HTMLFormElement>(null);
  
  useEffect(() => {
    const interval = setInterval(() => {
      if (formRef.current) {
        submit(formRef.current, {
          method: "post",
          replace: true,
        });
      }
    }, 5000);
    
    return () => clearInterval(interval);
  }, [submit]);
  
  return (
    <Form ref={formRef}>
      <input type="hidden" name="heartbeat" value="true" />
    </Form>
  );
}

Programmatic logout

function LogoutButton() {
  const submit = useSubmit();
  
  return (
    <button
      onClick={() => {
        submit(null, {
          method: "post",
          action: "/logout",
        });
      }}
    >
      Logout
    </button>
  );
}

Submit with loading state

function SaveButton({ data }) {
  const submit = useSubmit();
  const navigation = useNavigation();
  const isSubmitting = navigation.state === "submitting";
  
  return (
    <button
      onClick={() => submit(data, { method: "post" })}
      disabled={isSubmitting}
    >
      {isSubmitting ? "Saving..." : "Save"}
    </button>
  );
}

Submit multiple forms

function BulkActions({ items }) {
  const submit = useSubmit();
  
  const deleteAll = () => {
    items.forEach((item) => {
      submit(
        { id: item.id },
        {
          method: "post",
          action: "/delete",
          navigate: false,
          fetcherKey: `delete-${item.id}`,
        }
      );
    });
  };
  
  return <button onClick={deleteAll}>Delete All</button>;
}

Encoding Types

application/x-www-form-urlencoded (default)

submit(
  { name: "John", email: "[email protected]" },
  { method: "post" }
);
// Sent as: name=John&email=john%40example.com

multipart/form-data

const formData = new FormData();
formData.append("file", fileInput.files[0]);

submit(formData, {
  method: "post",
  encType: "multipart/form-data",
});

application/json

submit(
  {
    user: {
      name: "John",
      settings: { theme: "dark" },
    },
  },
  {
    method: "post",
    encType: "application/json",
  }
);
// Sent as: {"user":{"name":"John","settings":{"theme":"dark"}}}

Type Safety

interface FormData {
  name: string;
  email: string;
}

function Component() {
  const submit = useSubmit();
  
  const handleSubmit = (data: FormData) => {
    submit(data, {
      method: "post",
      action: "/api/users",
    });
  };
}

Build docs developers (and LLMs) love