Form component overview
Form is a progressively enhanced HTML form element that submits data to actions via fetch. It activates pending states in useNavigation, enabling advanced user interfaces. After the form action completes, all data on the page is automatically revalidated to keep the UI in sync. Server rendered pages are interactive at a basic level before JavaScript loads, with the browser managing submission and pending states. After JavaScript loads, React Router takes over for web application experiences. Form is most useful for submissions that change the URL or add an entry to browser history. For forms that should not manipulate browser history, use fetcher.Form instead.
Form basic example
Example of a Form component with action and method props:
```tsx
import { Form } from "react-router";
function NewEvent() {
return (
<Form action="/events" method="post">
<input name="title" type="text" />
<input name="description" type="text" />
</Form>
);
}
```
Form available in framework and data modes
Form is available in framework mode and data mode.
Form and fetcher API comparison
Navigation/URL APIs and Fetcher APIs provide similar functionality: <Form> corresponds to <fetcher.Form>, actionData (component prop) corresponds to fetcher.data, navigation.state corresponds to fetcher.state, navigation.formAction corresponds to fetcher.formAction, and navigation.formData corresponds to fetcher.formData.
Creating a new record example with Form and useNavigation
Example showing how to create a new record using <Form>, component props with actionData, and useNavigation. The action validates form data, creates the record in the database, and redirects to the new record's page. The component uses actionData to display validation errors and useNavigation to determine submission state for UI feedback.
```tsx
import {
Form,
redirect,
useNavigation,
} from "react-router";
import type { Route } from "./+types/new-recipe";
export async function action({
request,
}: Route.ActionArgs) {
const formData = await request.formData();
const errors = await validateRecipeFormData(formData);
if (errors) {
return { errors };
}
const recipe = await db.recipes.create(formData);
return redirect(`/recipes/${recipe.id}`);
}
export function NewRecipe({
actionData,
}: Route.ComponentProps) {
const { errors } = actionData || {};
const navigation = useNavigation();
const isSubmitting =
navigation.formAction === "/recipes/new";
return (
<Form method="post">
<label>
Title: <input name="title" />
{errors?.title ? <span>{errors.title}</span> : null}
</label>
<label>
Ingredients: <textarea name="ingredients" />
{errors?.ingredients ? (
<span>{errors.ingredients}</span>
) : null}
</label>
<label>
Directions: <textarea name="directions" />
{errors?.directions ? (
<span>{errors.directions}</span>
) : null}
</label>
<button type="submit">
{isSubmitting ? "Saving..." : "Create Recipe"}
</button>
</Form>
);
}
```
Deleting a record from a list with useFetcher
Example showing how to delete a record from a list using useFetcher without changing the URL or navigating away. The action deletes the recipe from the database and returns {ok: true}. The RecipeListItem component uses useFetcher to submit a form with the recipe id and manages the isDeleting state to provide UI feedback. Each fetcher independently manages its own state.
```tsx
import { useFetcher } from "react-router";
import type { Recipe } from "./recipe.server";
import type { Route } from "./+types/recipes";
export async function action({
request,
}: Route.ActionArgs) {
const formData = await request.formData();
const id = formData.get("id");
await db.recipes.delete(id);
return { ok: true };
}
function RecipeListItem({ recipe }: { recipe: Recipe }) {
const fetcher = useFetcher();
const isDeleting = fetcher.state !== "idle";
return (
<li>
<h2>{recipe.title}</h2>
<fetcher.Form method="post">
<input type="hidden" name="id" value={recipe.id} />
<button disabled={isDeleting} type="submit">
{isDeleting ? "Deleting..." : "Delete"}
</button>
</fetcher.Form>
</li>
);
}
```
Mark article as read with useFetcher
Example of using useFetcher.submit() to mark an article as read after the user has spent time on the page and scrolled to the bottom, without changing the URL or navigating away.
```tsx
import { useFetcher } from "react-router";
function useMarkAsRead({ articleId, userId }) {
const marker = useFetcher();
useSpentSomeTimeHereAndScrolledToTheBottom(() => {
marker.submit(
{ userId },
{
action: `/article/${articleId}/mark-as-read`,
method: "post",
},
);
});
}
```
User avatar details popup with useFetcher.load()
Example showing how to use useFetcher to load data on demand for a popup component. The UserAvatar component uses useFetcher with a loader to fetch user details on hover and display them in a popup, without navigating away or changing the URL.
```tsx
import { useState, useEffect } from "react";
import { useFetcher } from "react-router";
import type { Route } from "./+types/user-details";
export async function loader({ params }: Route.LoaderArgs) {
return await fakeDb.user.find({
where: { id: params.id },
});
}
type LoaderData = Route.ComponentProps["loaderData"];
function UserAvatar({ partialUser }) {
const userDetails = useFetcher<LoaderData>();
const [showDetails, setShowDetails] = useState(false);
useEffect(() => {
if (
showDetails &&
userDetails.state === "idle" &&
!userDetails.data
) {
userDetails.load(`/user-details/${partialUser.id}`);
}
}, [showDetails, userDetails, partialUser.id]);
return (
<div
onMouseEnter={() => setShowDetails(true)}
onMouseLeave={() => setShowDetails(false)}
>
<img src={partialUser.profileImageUrl} />
{showDetails ? (
userDetails.state === "idle" && userDetails.data ? (
<UserPopup user={userDetails.data} />
) : (
<UserPopupLoading />
)
) : null}
</div>
);
}
```
fetcher.Form method prop
The Form component returned by useFetcher accepts a method prop. When method="post" is specified, the form will submit as a POST request to the route's action function.
Form validation pattern with fetcher
A common pattern for form validation in React Router is: (1) render a form using fetcher.Form, (2) define an action that validates the submitted data, (3) if validation fails, return the errors object with a 400 status using data(), (4) access the errors through fetcher.data in the component and display them conditionally.
action export function
Route actions allow server-side data mutations with automatic revalidation of all loader data on the page when called from Form, useFetcher, and useSubmit. Actions are called with a request object and can return data that is passed to the component via actionData prop.
clientAction export function
clientAction is like route actions but only called in the browser. It receives a serverAction argument and can call the server action if needed, or perform client-side mutations.
Route module example with loader and action
Example showing a route module with loader that fetches items, an action that adds items, and a component that displays the items and includes a Form for creating new todos. The action automatically triggers revalidation of the loader.
clientAction example
Example showing clientAction that invalidates client-side cache and optionally calls the server action to get data.