
React Router Framework Mode
Install this when you are shipping React Router framework apps and need consistent patterns for search forms, POST mutations, and useFetcher inline updates.
Overview
React Router framework-mode (actions and form handling) is an agent skill for the Build phase that teaches Form, action, and useFetcher patterns for mutations and search in React Router apps.
Install
npx skills add https://github.com/remix-run/agent-skills --skill react-router-framework-modeWhat is this skill?
- Decision table for GET search forms vs POST redirects vs useFetcher mutations
- Default recommendation: useFetcher for smoother mutations without full navigation
- Documents automatic loader revalidation after actions complete
- Calls out anti-patterns like manual useSearchParams submit handlers for search
- Covers validation and optimistic UI tags from the skill metadata
- 4-row pattern selection table
Adoption & trust: 2.5k installs on skills.sh; 138 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You are unsure whether to use Form POST, GET search params, or useFetcher for each mutation on a React Router page.
Who is it for?
React Router framework routes where multiple mutations or filters share one page.
Skip if: Non-React stacks, client-only SPAs without route actions, or backend-only API design.
When should I use this skill?
Implementing or reviewing React Router form submission, useFetcher mutations, actions, or validation in framework mode.
What do I get? / Deliverables
You pick the framework-native pattern per use case and avoid manual form hacks that fight automatic loader revalidation.
- Chosen mutation pattern per feature
- Form/useFetcher implementations aligned with skill anti-patterns
Recommended Skills
Journey fit
Build → frontend is the canonical shelf because the skill encodes UI mutation and form routing patterns in React Router framework mode. Frontend subphase matches loader/action revalidation, Form components, and fetcher-driven optimistic UX—not backend API design alone.
How it compares
Skill playbook for React Router data APIs—not a generic React Hook Form tutorial.
Common Questions / FAQ
Who is react-router-framework-mode for?
Indie and solo frontend builders using React Router framework mode who want agent help choosing Form versus useFetcher for mutations and filters.
When should I use react-router-framework-mode?
Use it in Build frontend while implementing actions, validation, optimistic UI, or search forms that should update URL params or revalidate loaders.
Is react-router-framework-mode safe to install?
It is documentation-style guidance; review the Security Audits panel on this page before adding third-party agent skills to your repo.
SKILL.md
READMESKILL.md - React Router Framework Mode
# Actions and Form Handling Actions handle data mutations (create, update, delete). After an action completes, all loaders on the page automatically revalidate. ## Choosing the Right Pattern | Use Case | Pattern | Why | | ---------------------------- | ---------------------- | ---------------------------------- | | Search/filter forms | `<Form method="get">` | Auto-updates URL search params | | Mutations that navigate | `<Form method="post">` | Creates, then redirects | | Mutations without navigation | `useFetcher` | Ratings, likes, inline edits | | Multiple mutations on page | `useFetcher` | Each fetcher has independent state | **Default to `useFetcher` for mutations** - it provides smoother UX with optimistic updates and no page reload. ## Anti-Patterns to Avoid ```tsx // ❌ DON'T: Manual form handling for search function SearchForm() { const [searchParams, setSearchParams] = useSearchParams(); function onSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); const formData = new FormData(e.currentTarget); setSearchParams({ q: formData.get("q") as string }); } return ( <form onSubmit={onSubmit}> <input name="q" /> <button type="submit">Search</button> </form> ); } // ✅ DO: Use Form with method="get" - handles search params automatically function SearchForm() { return ( <Form method="get"> <input name="q" /> <button type="submit">Search</button> </Form> ); } ``` ```tsx // ❌ DON'T: Use Form for inline mutations (causes full navigation) function RatingButton({ itemId }) { return ( <Form method="post" action={`/items/${itemId}/rate`}> <button>Rate</button> </Form> ); } // ✅ DO: Use useFetcher for inline mutations (no navigation, smoother UX) function RatingButton({ itemId, currentRating }) { const fetcher = useFetcher(); // Optimistic UI - show expected state immediately const optimisticRating = fetcher.formData ? Number(fetcher.formData.get("rating")) : currentRating; return ( <fetcher.Form method="post" action={`/items/${itemId}/rate`}> <input type="hidden" name="rating" value={optimisticRating + 1} /> <button>⭐ {optimisticRating}</button> </fetcher.Form> ); } ``` --- ## Search Forms with GET `<Form method="get">` automatically serializes inputs to URL search params: ```tsx import { Form, useSearchParams } from "react-router"; export default function SearchPage() { const [searchParams] = useSearchParams(); const query = searchParams.get("q") ?? ""; return ( <div> <Form method="get"> <input type="text" name="q" defaultValue={query} /> <button type="submit">Search</button> </Form> {/* Results render here */} </div> ); } // Loader receives search params via request.url export async function loader({ request }: Route.LoaderArgs) { const url = new URL(request.url); const query = url.searchParams.get("q") ?? ""; return { results: await search(query) }; } ``` --- ## Server Action Runs on the server: ```tsx import { redirect, data } from "react-router"; export async function action({ request }: Route.ActionArgs) { const formData = await request.formData(); const title = formData.get("title"); await db.createProject({ title }); return redirect("/projects"); } ``` ## Client Action Runs in the browser: ```tsx export async function clientAction({ request, serverAction, }: Route.ClientActionArgs) { const formData = await request.formData(); // Can call server action const result = await serverAction(); // Or handle entirely on client await clientApi.update(formData); return result; }