
React Router Data Mode
Guide your coding agent through React Router actions, forms, fetchers, and server-side mutations without manual fetch boilerplate.
Overview
React Router data-mode is an agent skill for the Build phase that teaches actions, `<Form>`, and `useFetcher` patterns for mutations and validation in React Router data mode.
Install
npx skills add https://github.com/remix-run/agent-skills --skill react-router-data-modeWhat is this skill?
- Decision matrix for GET search forms vs POST mutations vs `useFetcher` inline edits
- Recommends `useFetcher` as the default for smoother mutations and optimistic UX
- Documents anti-patterns such as manual `useSearchParams` submit handlers for search
- Explains automatic loader revalidation after actions complete
- Maps mutations with navigation vs without navigation to the right Router APIs
- 4-row pattern matrix for GET search, POST navigate, and useFetcher mutations
Adoption & trust: 540 installs on skills.sh; 138 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You know React Router loaders but keep choosing the wrong mutation pattern and end up with reload-heavy forms or fragile manual search-param code.
Who is it for?
Solo builders implementing React Router v7/Remix-style data routes with forms, optimistic inline updates, or multi-mutation pages.
Skip if: Teams on purely client-only SPAs without route actions, or projects that already standardized on a separate API layer with no Router mutations.
When should I use this skill?
Implementing or reviewing React Router routes that need actions, `<Form>`, `useFetcher`, or validation in data mode.
What do I get? / Deliverables
After applying the skill, route modules use the documented form/fetcher matrix so mutations revalidate loaders and match UX expectations without ad-hoc client state hacks.
- Route modules using the recommended Form or useFetcher pattern
- Reduced anti-pattern form handlers
Recommended Skills
Journey fit
How it compares
Use for Router-native mutations instead of generic React form tutorials that ignore `useActionData` and loader revalidation.
Common Questions / FAQ
Who is react-router-data-mode for?
Indie and solo frontend builders using React Router data mode who want agent-guided patterns for actions, forms, and fetchers.
When should I use react-router-data-mode?
During Build when adding search filters, POST creates, inline likes or ratings, or any mutation where you must pick between `<Form>` and `useFetcher`.
Is react-router-data-mode safe to install?
Treat it as instructional agent guidance only; review the Security Audits panel on this Prism page before trusting third-party skill packages in your repo.
SKILL.md
READMESKILL.md - React Router Data 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> ); } ``` --- ## Defining Actions on Routes Actions are defined on route objects and handle form submissions: ```tsx import { redirect } from "react-router"; const router = createBrowserRouter([ { path: "/projects/new", action: async ({ request }) => { const formData = await request.formData(); const title = formData.get("title"); const project = await db.createProject({ title }); return redirect(`/projects/${project.id}`); }, Component: NewProject, }, ]); ``` ### Action Arguments - `request` - The Fetch Request object with form data - `params` - URL parameters from dynamic segments ```tsx { path: "/projects/:projectId", action: async ({ request, params }) => { const formData = await request.formData(); await db.updateProject(params.projectId, { title: formData.get("title"), }); return { success: true }; }, Component: EditProject, } ``` --- ## Search Forms with GET `<Form method="get">` automatically serializes inputs to URL search params: ```tsx import { Form, useSearchParams, useLoaderData } from "react-router"; const router = createBrowserRouter([ { path: "/search", loader: async ({ request }) => { const url = new URL(request.url); const query = url.searchParams.get("q") ?? ""; return { results: await search(query) }; }, Component: SearchPage, }, ]); function SearchPage() { const [