Actions and forms
An action is a function a route module exports under the name action. It runs on the server when a form is submitted to the route's address with POST (or PUT, PATCH, DELETE). Such a form is submitted with the Form component — an ordinary form tag to which the framework adds submission without a page reload.
The save function
Add a function for creating a post to app/data.server.ts:
// app/data.server.ts
export async function createPost(title: string, body: string) {
const post: Post = { id: posts.length + 1, title, body };
posts.push(post);
return post;
}A page with a form
Add the route route("posts/new", "routes/new-post.tsx") and create the file:
// app/routes/new-post.tsx
import { Form, redirect } from "react-router";
import type { Route } from "./+types/new-post";
import { createPost } from "../data.server";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const title = String(formData.get("title"));
const body = String(formData.get("body"));
const post = await createPost(title, body);
return redirect(`/posts/${post.id}`);
}
export default function NewPost() {
return (
<Form method="post">
<input name="title" placeholder="Title" />
<textarea name="body" placeholder="Post text" />
<button type="submit">Publish</button>
</Form>
);
}Open http://localhost:5173/posts/new, fill in the fields and click "Publish". The form goes to the same address, action runs, and redirect opens the new post's page. Back on the home page you will see the post in the list.
How it works
Formintercepts the submission and sends the data to the server with a POST request.- On the server
actionruns, andrequest.formData()returns the form fields by theirnameattributes. - After the
actionthe framework calls the page loaders again — the data on screen is always fresh. redirectfromreact-routerresponds with a move to another address.
Without JavaScript the form still works: the browser submits it the ordinary way, and the server responds with a redirect.
Linking to the form
On the home page add a link to the new page:
// app/routes/home.tsx
<Link to="/posts/new">Write a post</Link>Submitting to another address
The action attribute of Form says which route handles the submission. For example, a delete button can live on the post page:
// app/routes/post.tsx
<Form method="post" action={`/posts/${post.id}/delete`}>
<button type="submit">Delete</button>
</Form>You need a posts/:postId/delete route with an action function; it does not need a component.
Common mistake
A field without a name attribute does not end up in formData: formData.get("title") returns null, and String(null) turns into the string "null" — which then becomes the post title. Make sure every field has a name that matches the key the action reads.