Building a Multi-Page SPA with React RouterEntry 16 of 20
Programmatic Submission with useSubmit
useSubmit() returns a function that triggers a route action from code, without a <Form>.
Notes
- Behaves like
<Form>: calls the action, revalidates loaders, follows redirects. - Pass data and options:
submit(null, { method: 'delete' })orsubmit({ title }, { method: 'post', action: '/events/new' }). - Useful for confirm-before-submit flows, custom buttons, or submitting on change.
What / Why / When
- What
Imperative form submission.
- Why
Full control over when a submission happens.
- When
A delete button with a confirmation, auto-save, non-form triggers.
Code compare
// components/EventItem.tsximport { Link, useSubmit } from "react-router";export default function EventItemView({ event }: { event: EventItem }) { const submit = useSubmit(); function startDeleteHandler() { if (window.confirm("Are you sure?")) { submit(null, { method: "delete" }); } } return ( <article> <h1>{event.title}</h1> <menu> <Link to="edit">Edit</Link> <button onClick={startDeleteHandler}>Delete</button> </menu> </article> );}// pages/EventDetail.tsxexport async function action({ params, request }: ActionFunctionArgs) { await deleteEvent(params.eventId!, request.method); return redirect("/events");}// services/events.tsexport async function deleteEvent(id: string, method: string) { const response = await fetch(`${API}/events/${id}`, { method }); if (!response.ok) throw new Error("Could not delete event.");}Submission in Angular is already programmatic: call the service from a method.
export class EventItemComponent { private eventsService = inject(EventsService); private router = inject(Router); event = input.required<EventItem>(); startDelete() { if (!confirm("Are you sure?")) return; this.eventsService .deleteEvent(this.event().id) .subscribe(() => this.router.navigate(["/events"])); }}Quick reference
| React | Angular |
|---|---|
submit(data, { method, action }) | Component method → service |
Action handles delete + redirect() | subscribe → router.navigate() |