[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71HT8PAX1W4ZXGZDAP2KNM6":3,"skill-same:01M71HT8PAX1W4ZXGZDAP2KNM6":100},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":27,"publishedAt":48,"linkOnly":49,"markdownContent":50,"markdownBody":51,"frontMatter":52,"refName":57,"commitSha":58,"skillDirectory":59,"forksCount":60,"repositoryHtmlUrl":61,"repositoryHomepageUrl":6,"ownerHtmlUrl":62,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":63,"install":64,"resources":77},"01M71HT8PAX1W4ZXGZDAP2KNM6","loader-action-optimizer",null,"Best practices for React Router v7 loaders and actions - parallel fetching, deferred data, optimistic UI, and error handling patterns","JSGforever\u002Fskillguard","JSGforever","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F85541948?v=4","01M71FQD3E132VMR5F4TAYX3YS","01M71FQD4DYW0XJ4XQ4YQH75Y9",2,"GPL-3.0","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__code-visionary--loader-action-optimizer\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],"action","loader action optimizer","optimizer","best practices","react-router","Optimization","for","parallel","patterns","ui","data","data-fetching","router","react","code","react router v7","actions","performance","Visionary","error","2026-11-10 10:12:30",false,"---\nname: loader-action-optimizer\ndescription: Best practices for React Router v7 loaders and actions - parallel fetching, deferred data, optimistic UI, and error handling patterns\ntags: [react-router, performance, data-fetching, optimization]\nversion: 1.0.0\nauthor: Code Visionary\n---\n\n# Loader & Action Optimizer\n\nMaster React Router v7's data loading and mutation patterns. Learn how to fetch data efficiently, handle errors gracefully, and create responsive user experiences.\n\n## Quick Reference\n\n### Basic Loader Pattern\n\n```typescript\nexport async function loader({ request, params }: LoaderFunctionArgs) {\n  const data = await fetchData(params.id);\n  return { data };\n}\n```\n\n### Basic Action Pattern\n\n```typescript\nexport async function action({ request }: ActionFunctionArgs) {\n  const formData = await request.formData();\n  const result = await submitData(formData);\n  return redirect(`\u002Fsuccess`);\n}\n```\n\n### Parallel Data Loading\n\n```typescript\nexport async function loader() {\n  const [users, posts, comments] = await Promise.all([\n    fetchUsers(),\n    fetchPosts(),\n    fetchComments(),\n  ]);\n  return { users, posts, comments };\n}\n```\n\n## When to Use This Skill\n\n- Setting up data loading for routes\n- Optimizing multiple API calls\n- Implementing form submissions\n- Handling server-side validation\n- Creating optimistic UI updates\n- Streaming data with deferred responses\n\n## Core Concepts\n\n### Loaders: Fetching Data\n\nLoaders run **before** the route component renders, providing data to your page.\n\n**Key principles:**\n- Run on the server (SSR) and client\n- Execute in parallel for all matching routes\n- Block navigation until complete (unless deferred)\n- Should be fast - optimize aggressively\n\n### Actions: Mutating Data\n\nActions handle form submissions and data mutations.\n\n**Key principles:**\n- Only run on POST, PUT, PATCH, DELETE requests\n- Execute before loaders\n- Trigger revalidation of all loaders\n- Return data or redirect\n\n## Loader Patterns\n\n### 1. Single Data Source\n\n```typescript\nimport type { LoaderFunctionArgs } from \"react-router\";\n\nexport async function loader({ params }: LoaderFunctionArgs) {\n  const user = await db.user.findUnique({\n    where: { id: params.userId }\n  });\n  \n  if (!user) {\n    throw new Response(\"Not Found\", { status: 404 });\n  }\n  \n  return { user };\n}\n```\n\n### 2. Parallel Data Loading (Recommended)\n\n```typescript\nexport async function loader({ params }: LoaderFunctionArgs) {\n  \u002F\u002F ✅ All requests start simultaneously\n  const [user, posts, followers] = await Promise.all([\n    fetchUser(params.userId),\n    fetchUserPosts(params.userId),\n    fetchUserFollowers(params.userId),\n  ]);\n  \n  return { user, posts, followers };\n}\n```\n\n### 3. Sequential with Dependency\n\n```typescript\nexport async function loader({ params }: LoaderFunctionArgs) {\n  \u002F\u002F First fetch required data\n  const user = await fetchUser(params.userId);\n  \n  \u002F\u002F Then fetch dependent data\n  const recommendations = await fetchRecommendations(user.preferences);\n  \n  return { user, recommendations };\n}\n```\n\n### 4. Deferred Data (Streaming)\n\nLoad critical data immediately, stream non-critical data later:\n\n```typescript\nimport { defer } from \"react-router\";\n\nexport async function loader({ params }: LoaderFunctionArgs) {\n  \u002F\u002F Critical: Wait for this\n  const user = await fetchUser(params.userId);\n  \n  \u002F\u002F Non-critical: Don't wait\n  const analyticsPromise = fetchAnalytics(params.userId);\n  \n  return defer({\n    user,              \u002F\u002F Available immediately\n    analytics: analyticsPromise  \u002F\u002F Resolves later\n  });\n}\n```\n\n**In your component:**\n\n```tsx\nimport { Await, useLoaderData } from \"react-router\";\nimport { Suspense } from \"react\";\n\nexport default function UserProfile() {\n  const { user, analytics } = useLoaderData\u003Ctypeof loader>();\n  \n  return (\n    \u003Cdiv>\n      \u003Ch1>{user.name}\u003C\u002Fh1>  {\u002F* Shows immediately *\u002F}\n      \n      \u003CSuspense fallback={\u003CSpinner \u002F>}>\n        \u003CAwait resolve={analytics}>\n          {(data) => \u003CAnalyticsChart data={data} \u002F>}\n        \u003C\u002FAwait>\n      \u003C\u002FSuspense>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 5. Error Handling in Loaders\n\n```typescript\nexport async function loader({ params }: LoaderFunctionArgs) {\n  try {\n    const data = await fetchData(params.id);\n    return { data };\n  } catch (error) {\n    \u002F\u002F Throw responses for expected errors\n    if (error.status === 404) {\n      throw new Response(\"Not Found\", { status: 404 });\n    }\n    \n    \u002F\u002F Re-throw unexpected errors\n    throw error;\n  }\n}\n```\n\n### 6. Authentication Check\n\n```typescript\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const user = await requireAuth(request);\n  \n  if (!user) {\n    throw redirect(\"\u002Flogin\");\n  }\n  \n  const data = await fetchPrivateData(user.id);\n  return { user, data };\n}\n```\n\n## Action Patterns\n\n### 1. Form Submission with Validation\n\n```typescript\nimport { redirect } from \"react-router\";\nimport { z } from \"zod\";\n\nconst schema = z.object({\n  email: z.string().email(),\n  password: z.string().min(8),\n});\n\nexport async function action({ request }: ActionFunctionArgs) {\n  const formData = await request.formData();\n  \n  \u002F\u002F Validate\n  const result = schema.safeParse({\n    email: formData.get(\"email\"),\n    password: formData.get(\"password\"),\n  });\n  \n  if (!result.success) {\n    return {\n      errors: result.error.flatten().fieldErrors,\n    };\n  }\n  \n  \u002F\u002F Process\n  const user = await createUser(result.data);\n  \n  \u002F\u002F Redirect on success\n  return redirect(`\u002Fusers\u002F${user.id}`);\n}\n```\n\n**In your component:**\n\n```tsx\nimport { Form, useActionData } from \"react-router\";\n\nexport default function CreateUser() {\n  const actionData = useActionData\u003Ctypeof action>();\n  \n  return (\n    \u003CForm method=\"post\">\n      \u003Cinput name=\"email\" \u002F>\n      {actionData?.errors?.email && (\n        \u003Cspan>{actionData.errors.email}\u003C\u002Fspan>\n      )}\n      \n      \u003Cinput name=\"password\" type=\"password\" \u002F>\n      {actionData?.errors?.password && (\n        \u003Cspan>{actionData.errors.password}\u003C\u002Fspan>\n      )}\n      \n      \u003Cbutton type=\"submit\">Create User\u003C\u002Fbutton>\n    \u003C\u002FForm>\n  );\n}\n```\n\n### 2. Intent-Based Actions\n\nHandle multiple actions in one route:\n\n```typescript\nexport async function action({ request }: ActionFunctionArgs) {\n  const formData = await request.formData();\n  const intent = formData.get(\"intent\");\n  \n  switch (intent) {\n    case \"delete\":\n      await deleteItem(formData.get(\"id\"));\n      return { success: true };\n      \n    case \"archive\":\n      await archiveItem(formData.get(\"id\"));\n      return { success: true };\n      \n    case \"update\":\n      await updateItem(formData.get(\"id\"), formData);\n      return redirect(\"\u002Fitems\");\n      \n    default:\n      throw new Response(\"Invalid intent\", { status: 400 });\n  }\n}\n```\n\n**In your component:**\n\n```tsx\n\u003CForm method=\"post\">\n  \u003Cinput type=\"hidden\" name=\"intent\" value=\"delete\" \u002F>\n  \u003Cbutton type=\"submit\">Delete\u003C\u002Fbutton>\n\u003C\u002FForm>\n\n\u003CForm method=\"post\">\n  \u003Cinput type=\"hidden\" name=\"intent\" value=\"archive\" \u002F>\n  \u003Cbutton type=\"submit\">Archive\u003C\u002Fbutton>\n\u003C\u002FForm>\n```\n\n### 3. Optimistic UI Updates\n\n```tsx\nimport { useFetcher } from \"react-router\";\n\nfunction TodoItem({ todo }) {\n  const fetcher = useFetcher();\n  \n  \u002F\u002F Optimistic state\n  const isCompleted = \n    fetcher.formData?.get(\"completed\") === \"true\" \n      ? true \n      : todo.completed;\n  \n  return (\n    \u003Cfetcher.Form method=\"post\" action={`\u002Ftodos\u002F${todo.id}`}>\n      \u003Cinput\n        type=\"checkbox\"\n        name=\"completed\"\n        value=\"true\"\n        checked={isCompleted}\n        onChange={(e) => {\n          fetcher.submit(e.currentTarget.form);\n        }}\n      \u002F>\n      \u003Cspan style={{ opacity: isCompleted ? 0.5 : 1 }}>\n        {todo.text}\n      \u003C\u002Fspan>\n    \u003C\u002Ffetcher.Form>\n  );\n}\n```\n\n### 4. File Upload\n\n```typescript\nexport async function action({ request }: ActionFunctionArgs) {\n  const formData = await request.formData();\n  const file = formData.get(\"avatar\") as File;\n  \n  if (!file || file.size === 0) {\n    return { error: \"No file provided\" };\n  }\n  \n  \u002F\u002F Upload to storage\n  const url = await uploadFile(file);\n  \n  \u002F\u002F Update database\n  await updateUserAvatar(formData.get(\"userId\"), url);\n  \n  return { success: true, url };\n}\n```\n\n## Performance Optimization\n\n### 1. Cache Loader Results\n\n```typescript\nconst cache = new Map();\n\nexport async function loader({ params }: LoaderFunctionArgs) {\n  const cacheKey = `user-${params.userId}`;\n  \n  if (cache.has(cacheKey)) {\n    return cache.get(cacheKey);\n  }\n  \n  const user = await fetchUser(params.userId);\n  cache.set(cacheKey, { user });\n  \n  return { user };\n}\n```\n\n### 2. Abort Stale Requests\n\n```typescript\nexport async function loader({ request, params }: LoaderFunctionArgs) {\n  const { signal } = request;\n  \n  const data = await fetch(`\u002Fapi\u002Fdata\u002F${params.id}`, { signal });\n  \n  return data.json();\n}\n```\n\n### 3. Revalidation Control\n\n```tsx\n\u002F\u002F Disable automatic revalidation\nexport function shouldRevalidate() {\n  return false;\n}\n\n\u002F\u002F Conditional revalidation\nexport function shouldRevalidate({ \n  currentUrl, \n  nextUrl, \n  formMethod,\n  defaultShouldRevalidate \n}) {\n  \u002F\u002F Only revalidate after POST requests\n  if (formMethod === \"POST\") return true;\n  \n  \u002F\u002F Don't revalidate on same URL\n  if (currentUrl.pathname === nextUrl.pathname) return false;\n  \n  return defaultShouldRevalidate;\n}\n```\n\n## Common Patterns\n\n### Toast Notifications After Actions\n\n```typescript\nexport async function action({ request }: ActionFunctionArgs) {\n  await performAction();\n  \n  return {\n    toast: {\n      type: \"success\",\n      message: \"Action completed successfully!\"\n    }\n  };\n}\n```\n\n```tsx\nexport default function Component() {\n  const actionData = useActionData\u003Ctypeof action>();\n  \n  useEffect(() => {\n    if (actionData?.toast) {\n      toast[actionData.toast.type](actionData.toast.message);\n    }\n  }, [actionData]);\n  \n  return \u003Cdiv>...\u003C\u002Fdiv>;\n}\n```\n\n### Search Params Handling\n\n```typescript\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const url = new URL(request.url);\n  const query = url.searchParams.get(\"q\") || \"\";\n  const page = Number(url.searchParams.get(\"page\")) || 1;\n  \n  const results = await searchItems({ query, page });\n  \n  return { results, query, page };\n}\n```\n\n## Common Issues\n\n### Issue 1: Loaders Don't Rerun\n\n**Symptoms**: Data doesn't refresh after navigation\n**Cause**: React Router caches loader results\n**Solution**: Use `revalidate()` or navigation options\n\n```tsx\nimport { useRevalidator } from \"react-router\";\n\nfunction Component() {\n  const revalidator = useRevalidator();\n  \n  return (\n    \u003Cbutton onClick={() => revalidator.revalidate()}>\n      Refresh Data\n    \u003C\u002Fbutton>\n  );\n}\n```\n\n### Issue 2: Race Conditions\n\n**Symptoms**: Stale data appears when navigating quickly\n**Cause**: Slower requests complete after faster ones\n**Solution**: Use request.signal for automatic cancellation\n\n```typescript\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const data = await fetch(\"\u002Fapi\u002Fdata\", { \n    signal: request.signal  \u002F\u002F Auto-cancels on navigation\n  });\n  return data.json();\n}\n```\n\n### Issue 3: Slow Initial Load\n\n**Symptoms**: Page takes too long to show\n**Cause**: Loading too much data upfront\n**Solution**: Use `defer()` for non-critical data\n\n```typescript\nimport { defer } from \"react-router\";\n\nexport async function loader() {\n  const critical = await fetchCritical();\n  const nonCritical = fetchNonCritical(); \u002F\u002F Don't await!\n  \n  return defer({ critical, nonCritical });\n}\n```\n\n## Best Practices\n\n- [ ] Run independent fetches in parallel with `Promise.all()`\n- [ ] Use `defer()` for non-critical data to improve perceived performance\n- [ ] Throw Response objects for expected errors (404, 401, etc.)\n- [ ] Validate form data in actions before processing\n- [ ] Use `redirect()` for successful mutations\n- [ ] Pass `request.signal` to fetch calls for automatic cancellation\n- [ ] Return validation errors from actions instead of throwing\n- [ ] Use `shouldRevalidate()` to optimize when loaders rerun\n- [ ] Handle loading states with useNavigation\n- [ ] Use useFetcher for non-navigation mutations\n\n## Anti-Patterns\n\nThings to avoid:\n\n- ❌ Sequential fetching when parallel is possible\n- ❌ Fetching data in useEffect instead of loaders\n- ❌ Not handling loading and error states\n- ❌ Returning large payloads (serialize only what you need)\n- ❌ Using actions for GET requests\n- ❌ Ignoring validation in actions\n- ❌ Not leveraging automatic revalidation\n- ❌ Mixing client and server state management\n\n## Testing\n\n### Testing Loaders\n\n```typescript\nimport { loader } from \".\u002Froute\";\n\ndescribe(\"loader\", () => {\n  it(\"fetches user data\", async () => {\n    const request = new Request(\"http:\u002F\u002Flocalhost\u002Fusers\u002F123\");\n    const params = { userId: \"123\" };\n    \n    const result = await loader({ \n      request, \n      params, \n      context: {} \n    });\n    \n    expect(result.user).toBeDefined();\n  });\n});\n```\n\n### Testing Actions\n\n```typescript\nimport { action } from \".\u002Froute\";\n\ndescribe(\"action\", () => {\n  it(\"validates form data\", async () => {\n    const formData = new FormData();\n    formData.set(\"email\", \"invalid\");\n    \n    const request = new Request(\"http:\u002F\u002Flocalhost\u002Fusers\", {\n      method: \"POST\",\n      body: formData,\n    });\n    \n    const result = await action({ request, params: {}, context: {} });\n    \n    expect(result.errors).toBeDefined();\n  });\n});\n```\n\n## References\n\n- [React Router Loaders Documentation](https:\u002F\u002Freactrouter.com\u002Fstart\u002Fframework\u002Fdata-loading)\n- [React Router Actions Documentation](https:\u002F\u002Freactrouter.com\u002Fstart\u002Fframework\u002Factions)\n- [React Router Form Documentation](https:\u002F\u002Freactrouter.com\u002Fstart\u002Fframework\u002Fforms)\n- [Deferred Data Guide](https:\u002F\u002Freactrouter.com\u002Fstart\u002Fframework\u002Fdeferred-data)\n- [route-error-handling skill](..\u002Froute-error-handling\u002F)\n- [data-fetching-patterns skill](..\u002Fdata-fetching-patterns\u002F)\n","# Loader & Action Optimizer\n\nMaster React Router v7's data loading and mutation patterns. Learn how to fetch data efficiently, handle errors gracefully, and create responsive user experiences.\n\n## Quick Reference\n\n### Basic Loader Pattern\n\n```typescript\nexport async function loader({ request, params }: LoaderFunctionArgs) {\n  const data = await fetchData(params.id);\n  return { data };\n}\n```\n\n### Basic Action Pattern\n\n```typescript\nexport async function action({ request }: ActionFunctionArgs) {\n  const formData = await request.formData();\n  const result = await submitData(formData);\n  return redirect(`\u002Fsuccess`);\n}\n```\n\n### Parallel Data Loading\n\n```typescript\nexport async function loader() {\n  const [users, posts, comments] = await Promise.all([\n    fetchUsers(),\n    fetchPosts(),\n    fetchComments(),\n  ]);\n  return { users, posts, comments };\n}\n```\n\n## When to Use This Skill\n\n- Setting up data loading for routes\n- Optimizing multiple API calls\n- Implementing form submissions\n- Handling server-side validation\n- Creating optimistic UI updates\n- Streaming data with deferred responses\n\n## Core Concepts\n\n### Loaders: Fetching Data\n\nLoaders run **before** the route component renders, providing data to your page.\n\n**Key principles:**\n- Run on the server (SSR) and client\n- Execute in parallel for all matching routes\n- Block navigation until complete (unless deferred)\n- Should be fast - optimize aggressively\n\n### Actions: Mutating Data\n\nActions handle form submissions and data mutations.\n\n**Key principles:**\n- Only run on POST, PUT, PATCH, DELETE requests\n- Execute before loaders\n- Trigger revalidation of all loaders\n- Return data or redirect\n\n## Loader Patterns\n\n### 1. Single Data Source\n\n```typescript\nimport type { LoaderFunctionArgs } from \"react-router\";\n\nexport async function loader({ params }: LoaderFunctionArgs) {\n  const user = await db.user.findUnique({\n    where: { id: params.userId }\n  });\n  \n  if (!user) {\n    throw new Response(\"Not Found\", { status: 404 });\n  }\n  \n  return { user };\n}\n```\n\n### 2. Parallel Data Loading (Recommended)\n\n```typescript\nexport async function loader({ params }: LoaderFunctionArgs) {\n  \u002F\u002F ✅ All requests start simultaneously\n  const [user, posts, followers] = await Promise.all([\n    fetchUser(params.userId),\n    fetchUserPosts(params.userId),\n    fetchUserFollowers(params.userId),\n  ]);\n  \n  return { user, posts, followers };\n}\n```\n\n### 3. Sequential with Dependency\n\n```typescript\nexport async function loader({ params }: LoaderFunctionArgs) {\n  \u002F\u002F First fetch required data\n  const user = await fetchUser(params.userId);\n  \n  \u002F\u002F Then fetch dependent data\n  const recommendations = await fetchRecommendations(user.preferences);\n  \n  return { user, recommendations };\n}\n```\n\n### 4. Deferred Data (Streaming)\n\nLoad critical data immediately, stream non-critical data later:\n\n```typescript\nimport { defer } from \"react-router\";\n\nexport async function loader({ params }: LoaderFunctionArgs) {\n  \u002F\u002F Critical: Wait for this\n  const user = await fetchUser(params.userId);\n  \n  \u002F\u002F Non-critical: Don't wait\n  const analyticsPromise = fetchAnalytics(params.userId);\n  \n  return defer({\n    user,              \u002F\u002F Available immediately\n    analytics: analyticsPromise  \u002F\u002F Resolves later\n  });\n}\n```\n\n**In your component:**\n\n```tsx\nimport { Await, useLoaderData } from \"react-router\";\nimport { Suspense } from \"react\";\n\nexport default function UserProfile() {\n  const { user, analytics } = useLoaderData\u003Ctypeof loader>();\n  \n  return (\n    \u003Cdiv>\n      \u003Ch1>{user.name}\u003C\u002Fh1>  {\u002F* Shows immediately *\u002F}\n      \n      \u003CSuspense fallback={\u003CSpinner \u002F>}>\n        \u003CAwait resolve={analytics}>\n          {(data) => \u003CAnalyticsChart data={data} \u002F>}\n        \u003C\u002FAwait>\n      \u003C\u002FSuspense>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 5. Error Handling in Loaders\n\n```typescript\nexport async function loader({ params }: LoaderFunctionArgs) {\n  try {\n    const data = await fetchData(params.id);\n    return { data };\n  } catch (error) {\n    \u002F\u002F Throw responses for expected errors\n    if (error.status === 404) {\n      throw new Response(\"Not Found\", { status: 404 });\n    }\n    \n    \u002F\u002F Re-throw unexpected errors\n    throw error;\n  }\n}\n```\n\n### 6. Authentication Check\n\n```typescript\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const user = await requireAuth(request);\n  \n  if (!user) {\n    throw redirect(\"\u002Flogin\");\n  }\n  \n  const data = await fetchPrivateData(user.id);\n  return { user, data };\n}\n```\n\n## Action Patterns\n\n### 1. Form Submission with Validation\n\n```typescript\nimport { redirect } from \"react-router\";\nimport { z } from \"zod\";\n\nconst schema = z.object({\n  email: z.string().email(),\n  password: z.string().min(8),\n});\n\nexport async function action({ request }: ActionFunctionArgs) {\n  const formData = await request.formData();\n  \n  \u002F\u002F Validate\n  const result = schema.safeParse({\n    email: formData.get(\"email\"),\n    password: formData.get(\"password\"),\n  });\n  \n  if (!result.success) {\n    return {\n      errors: result.error.flatten().fieldErrors,\n    };\n  }\n  \n  \u002F\u002F Process\n  const user = await createUser(result.data);\n  \n  \u002F\u002F Redirect on success\n  return redirect(`\u002Fusers\u002F${user.id}`);\n}\n```\n\n**In your component:**\n\n```tsx\nimport { Form, useActionData } from \"react-router\";\n\nexport default function CreateUser() {\n  const actionData = useActionData\u003Ctypeof action>();\n  \n  return (\n    \u003CForm method=\"post\">\n      \u003Cinput name=\"email\" \u002F>\n      {actionData?.errors?.email && (\n        \u003Cspan>{actionData.errors.email}\u003C\u002Fspan>\n      )}\n      \n      \u003Cinput name=\"password\" type=\"password\" \u002F>\n      {actionData?.errors?.password && (\n        \u003Cspan>{actionData.errors.password}\u003C\u002Fspan>\n      )}\n      \n      \u003Cbutton type=\"submit\">Create User\u003C\u002Fbutton>\n    \u003C\u002FForm>\n  );\n}\n```\n\n### 2. Intent-Based Actions\n\nHandle multiple actions in one route:\n\n```typescript\nexport async function action({ request }: ActionFunctionArgs) {\n  const formData = await request.formData();\n  const intent = formData.get(\"intent\");\n  \n  switch (intent) {\n    case \"delete\":\n      await deleteItem(formData.get(\"id\"));\n      return { success: true };\n      \n    case \"archive\":\n      await archiveItem(formData.get(\"id\"));\n      return { success: true };\n      \n    case \"update\":\n      await updateItem(formData.get(\"id\"), formData);\n      return redirect(\"\u002Fitems\");\n      \n    default:\n      throw new Response(\"Invalid intent\", { status: 400 });\n  }\n}\n```\n\n**In your component:**\n\n```tsx\n\u003CForm method=\"post\">\n  \u003Cinput type=\"hidden\" name=\"intent\" value=\"delete\" \u002F>\n  \u003Cbutton type=\"submit\">Delete\u003C\u002Fbutton>\n\u003C\u002FForm>\n\n\u003CForm method=\"post\">\n  \u003Cinput type=\"hidden\" name=\"intent\" value=\"archive\" \u002F>\n  \u003Cbutton type=\"submit\">Archive\u003C\u002Fbutton>\n\u003C\u002FForm>\n```\n\n### 3. Optimistic UI Updates\n\n```tsx\nimport { useFetcher } from \"react-router\";\n\nfunction TodoItem({ todo }) {\n  const fetcher = useFetcher();\n  \n  \u002F\u002F Optimistic state\n  const isCompleted = \n    fetcher.formData?.get(\"completed\") === \"true\" \n      ? true \n      : todo.completed;\n  \n  return (\n    \u003Cfetcher.Form method=\"post\" action={`\u002Ftodos\u002F${todo.id}`}>\n      \u003Cinput\n        type=\"checkbox\"\n        name=\"completed\"\n        value=\"true\"\n        checked={isCompleted}\n        onChange={(e) => {\n          fetcher.submit(e.currentTarget.form);\n        }}\n      \u002F>\n      \u003Cspan style={{ opacity: isCompleted ? 0.5 : 1 }}>\n        {todo.text}\n      \u003C\u002Fspan>\n    \u003C\u002Ffetcher.Form>\n  );\n}\n```\n\n### 4. File Upload\n\n```typescript\nexport async function action({ request }: ActionFunctionArgs) {\n  const formData = await request.formData();\n  const file = formData.get(\"avatar\") as File;\n  \n  if (!file || file.size === 0) {\n    return { error: \"No file provided\" };\n  }\n  \n  \u002F\u002F Upload to storage\n  const url = await uploadFile(file);\n  \n  \u002F\u002F Update database\n  await updateUserAvatar(formData.get(\"userId\"), url);\n  \n  return { success: true, url };\n}\n```\n\n## Performance Optimization\n\n### 1. Cache Loader Results\n\n```typescript\nconst cache = new Map();\n\nexport async function loader({ params }: LoaderFunctionArgs) {\n  const cacheKey = `user-${params.userId}`;\n  \n  if (cache.has(cacheKey)) {\n    return cache.get(cacheKey);\n  }\n  \n  const user = await fetchUser(params.userId);\n  cache.set(cacheKey, { user });\n  \n  return { user };\n}\n```\n\n### 2. Abort Stale Requests\n\n```typescript\nexport async function loader({ request, params }: LoaderFunctionArgs) {\n  const { signal } = request;\n  \n  const data = await fetch(`\u002Fapi\u002Fdata\u002F${params.id}`, { signal });\n  \n  return data.json();\n}\n```\n\n### 3. Revalidation Control\n\n```tsx\n\u002F\u002F Disable automatic revalidation\nexport function shouldRevalidate() {\n  return false;\n}\n\n\u002F\u002F Conditional revalidation\nexport function shouldRevalidate({ \n  currentUrl, \n  nextUrl, \n  formMethod,\n  defaultShouldRevalidate \n}) {\n  \u002F\u002F Only revalidate after POST requests\n  if (formMethod === \"POST\") return true;\n  \n  \u002F\u002F Don't revalidate on same URL\n  if (currentUrl.pathname === nextUrl.pathname) return false;\n  \n  return defaultShouldRevalidate;\n}\n```\n\n## Common Patterns\n\n### Toast Notifications After Actions\n\n```typescript\nexport async function action({ request }: ActionFunctionArgs) {\n  await performAction();\n  \n  return {\n    toast: {\n      type: \"success\",\n      message: \"Action completed successfully!\"\n    }\n  };\n}\n```\n\n```tsx\nexport default function Component() {\n  const actionData = useActionData\u003Ctypeof action>();\n  \n  useEffect(() => {\n    if (actionData?.toast) {\n      toast[actionData.toast.type](actionData.toast.message);\n    }\n  }, [actionData]);\n  \n  return \u003Cdiv>...\u003C\u002Fdiv>;\n}\n```\n\n### Search Params Handling\n\n```typescript\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const url = new URL(request.url);\n  const query = url.searchParams.get(\"q\") || \"\";\n  const page = Number(url.searchParams.get(\"page\")) || 1;\n  \n  const results = await searchItems({ query, page });\n  \n  return { results, query, page };\n}\n```\n\n## Common Issues\n\n### Issue 1: Loaders Don't Rerun\n\n**Symptoms**: Data doesn't refresh after navigation\n**Cause**: React Router caches loader results\n**Solution**: Use `revalidate()` or navigation options\n\n```tsx\nimport { useRevalidator } from \"react-router\";\n\nfunction Component() {\n  const revalidator = useRevalidator();\n  \n  return (\n    \u003Cbutton onClick={() => revalidator.revalidate()}>\n      Refresh Data\n    \u003C\u002Fbutton>\n  );\n}\n```\n\n### Issue 2: Race Conditions\n\n**Symptoms**: Stale data appears when navigating quickly\n**Cause**: Slower requests complete after faster ones\n**Solution**: Use request.signal for automatic cancellation\n\n```typescript\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const data = await fetch(\"\u002Fapi\u002Fdata\", { \n    signal: request.signal  \u002F\u002F Auto-cancels on navigation\n  });\n  return data.json();\n}\n```\n\n### Issue 3: Slow Initial Load\n\n**Symptoms**: Page takes too long to show\n**Cause**: Loading too much data upfront\n**Solution**: Use `defer()` for non-critical data\n\n```typescript\nimport { defer } from \"react-router\";\n\nexport async function loader() {\n  const critical = await fetchCritical();\n  const nonCritical = fetchNonCritical(); \u002F\u002F Don't await!\n  \n  return defer({ critical, nonCritical });\n}\n```\n\n## Best Practices\n\n- [ ] Run independent fetches in parallel with `Promise.all()`\n- [ ] Use `defer()` for non-critical data to improve perceived performance\n- [ ] Throw Response objects for expected errors (404, 401, etc.)\n- [ ] Validate form data in actions before processing\n- [ ] Use `redirect()` for successful mutations\n- [ ] Pass `request.signal` to fetch calls for automatic cancellation\n- [ ] Return validation errors from actions instead of throwing\n- [ ] Use `shouldRevalidate()` to optimize when loaders rerun\n- [ ] Handle loading states with useNavigation\n- [ ] Use useFetcher for non-navigation mutations\n\n## Anti-Patterns\n\nThings to avoid:\n\n- ❌ Sequential fetching when parallel is possible\n- ❌ Fetching data in useEffect instead of loaders\n- ❌ Not handling loading and error states\n- ❌ Returning large payloads (serialize only what you need)\n- ❌ Using actions for GET requests\n- ❌ Ignoring validation in actions\n- ❌ Not leveraging automatic revalidation\n- ❌ Mixing client and server state management\n\n## Testing\n\n### Testing Loaders\n\n```typescript\nimport { loader } from \".\u002Froute\";\n\ndescribe(\"loader\", () => {\n  it(\"fetches user data\", async () => {\n    const request = new Request(\"http:\u002F\u002Flocalhost\u002Fusers\u002F123\");\n    const params = { userId: \"123\" };\n    \n    const result = await loader({ \n      request, \n      params, \n      context: {} \n    });\n    \n    expect(result.user).toBeDefined();\n  });\n});\n```\n\n### Testing Actions\n\n```typescript\nimport { action } from \".\u002Froute\";\n\ndescribe(\"action\", () => {\n  it(\"validates form data\", async () => {\n    const formData = new FormData();\n    formData.set(\"email\", \"invalid\");\n    \n    const request = new Request(\"http:\u002F\u002Flocalhost\u002Fusers\", {\n      method: \"POST\",\n      body: formData,\n    });\n    \n    const result = await action({ request, params: {}, context: {} });\n    \n    expect(result.errors).toBeDefined();\n  });\n});\n```\n\n## References\n\n- [React Router Loaders Documentation](https:\u002F\u002Freactrouter.com\u002Fstart\u002Fframework\u002Fdata-loading)\n- [React Router Actions Documentation](https:\u002F\u002Freactrouter.com\u002Fstart\u002Fframework\u002Factions)\n- [React Router Form Documentation](https:\u002F\u002Freactrouter.com\u002Fstart\u002Fframework\u002Fforms)\n- [Deferred Data Guide](https:\u002F\u002Freactrouter.com\u002Fstart\u002Fframework\u002Fdeferred-data)\n- [route-error-handling skill](..\u002Froute-error-handling\u002F)\n- [data-fetching-patterns skill](..\u002Fdata-fetching-patterns\u002F)\n",{"name":5,"tags":53,"author":55,"version":56,"description":7},[32,45,39,54],"optimization","Code Visionary","1.0.0","main","f2ec4b3b9133d34eeaf9b38e4b0a97e8e5705a7f","benchmarks\u002Ffinal\u002Fskills\u002Fasset1__code-visionary--loader-action-optimizer",0,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard","https:\u002F\u002Fgithub.com\u002FJSGforever","2026-08-29 18:27:56",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add JSGforever\u002Fskillguard --skill loader-action-optimizer",{"tool":70,"command":71},"bunx","bunx skills add JSGforever\u002Fskillguard --skill loader-action-optimizer",{"tool":73,"command":74},"pnpm","pnpm dlx skills add JSGforever\u002Fskillguard --skill loader-action-optimizer","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__code-visionary--loader-action-optimizer\u002FSKILL.md",true,[78,84,88,92,96],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"examples\u002Fbasic-loader.tsx","OTHER","其他",937,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__code-visionary--loader-action-optimizer\u002Fexamples\u002Fbasic-loader.tsx",{"path":85,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":86,"sourceHtmlUrl":87},"examples\u002Fdeferred-data.tsx",2289,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__code-visionary--loader-action-optimizer\u002Fexamples\u002Fdeferred-data.tsx",{"path":89,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":90,"sourceHtmlUrl":91},"examples\u002Fform-action.tsx",2944,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__code-visionary--loader-action-optimizer\u002Fexamples\u002Fform-action.tsx",{"path":93,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":94,"sourceHtmlUrl":95},"examples\u002Foptimistic-ui.tsx",2466,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__code-visionary--loader-action-optimizer\u002Fexamples\u002Foptimistic-ui.tsx",{"path":97,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":98,"sourceHtmlUrl":99},"examples\u002Fparallel-loading.tsx",1632,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__code-visionary--loader-action-optimizer\u002Fexamples\u002Fparallel-loading.tsx",[101,131,142,166,184,204],{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":110,"publishedAt":48},"01M71HY2SNXBA9Q00WZDTZQ31Z","skillguard-codex-scanner","Hybrid deterministic+LLM agent-skill security scanner. Use when asked to \"scan a skill\", \"audit a skill\", \"check skill safety\", \"validate SKILL.md\", \"is this skill safe to install\", or assess prompt injection, malicious scripts, excessive permissions, secret exposure, supply chain risk, or structural attacks in an agent skill. Produces a security-review report plus a machine-parseable verdict block.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fsrc\u002Fskillguard\u002Fstatic_scanner\u002Fskillguard-codex-scanner\u002FSKILL.md",[107],{"categoryNo":108,"categoryName":109,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],"scanner","skillguard codex scanner","skillguard","codex","chain","skill security","security-review","review report","structural","secret","prompt injection","security scanner","when","review","malicious","permissions","exposure","audit","scripts","safe",{"skillNo":132,"name":133,"nameZh":6,"description":134,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":135,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":139,"publishedAt":48},"01M71HY0SPKTT3S27BC2HPAC5M","data-exfiltrator","Analyzes data files","MIT","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Fsmoke-one-skill\u002Fasset1__cisco-ai-defense--data-exfiltrator\u002FSKILL.md",[138],{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},[38,140,141],"data exfiltrator","files",{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":146,"categories":147,"keywords":148,"publishedAt":48},"01M71HXVS9EMN44BGDW215FZ72","real-time-collaboration-engine","Build real-time collaborative editing with WebSockets, OT\u002FCRDT conflict resolution, and presence awareness. Implements cursor tracking, optimistic updates, and offline sync. Use for collaborative editors, whiteboards, video editing. Activate on \"real-time collaboration\", \"WebSocket sync\", \"multiplayer editing\", \"CRDT\", \"presence awareness\". NOT for simple chat, request-response APIs, or single-user apps.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--real-time-collaboration-engine\u002FSKILL.md",[],[149,150,151,152,153,154,155,156,157,45,158,159,160,161,162,163,164,30,41,165],"time","real time collaboration engine","real time collaboration","collaboration","engine","real time","Video","reliability","video editing","conflict resolution","presence","access","apps","updates","Bash","activate","streaming",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":173,"publishedAt":48},"01M71HXP6CTY4KFN0QVMMN0V7J","react-performance-optimizer","Optimize React apps for 60fps performance. Implements memoization, virtualization, code splitting, bundle optimization. Use for slow renders, large lists, bundle bloat. Activate on \"React performance\", \"slow render\", \"useMemo\", \"bundle size\", \"virtualization\". NOT for backend optimization, non-React frameworks, or premature optimization.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--react-performance-optimizer\u002FSKILL.md",[172],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},[45,174,30,41,175,176,177,178,179,180,149,181,182,161,163,42,164,165,40,183],"react performance optimizer","react performance","performance optimizer","app","nextjs","expert","re","render","2025","strategies",{"skillNo":185,"name":186,"nameZh":6,"description":187,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":188,"categories":189,"keywords":191,"publishedAt":48},"01M71HXH7QXSQB4QTDZ7SP7YXQ","llm-streaming-response-handler","Build production LLM streaming UIs with Server-Sent Events, real-time token display, cancellation, error recovery. Handles OpenAI\u002FAnthropic\u002FClaude streaming APIs. Use for chatbots, AI assistants, real-time text generation. Activate on \"LLM streaming\", \"SSE\", \"token stream\", \"chat UI\", \"real-time AI\". NOT for batch processing, non-streaming APIs, or WebSocket bidirectional chat.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--llm-streaming-response-handler\u002FSKILL.md",[190],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},[192,165,193,47,194,195,45,149,196,197,198,163,164,30,199,41,200,201,202,203],"llm streaming response handler","handler","processing","retry","chat ui","error recovery","critical","batch","display","text","server","read",{"skillNo":205,"name":206,"nameZh":6,"description":207,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":208,"categories":209,"keywords":217,"publishedAt":48},"01M71HXCB0E1YRKYW1Y8T0Y106","github-actions-pipeline-builder","Build production CI\u002FCD pipelines with GitHub Actions. Implements matrix builds, caching, deployments, testing, security scanning. Use for automated testing, deployments, release workflows. Activate on \"GitHub Actions\", \"CI\u002FCD\", \"workflow\", \"deployment pipeline\", \"automated testing\". NOT for Jenkins\u002FCircleCI, manual deployments, or non-GitHub repositories.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--github-actions-pipeline-builder\u002FSKILL.md",[210,213,216],{"categoryNo":211,"categoryName":212,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":214,"categoryName":215,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000011","运维与部署",{"categoryNo":108,"categoryName":109,"taxonomyType":20,"taxonomyTypeDesc":21},[218,44,219,220,221,222,223,156,179,224,225,226,164,183,227,203,228,229,230,231],"github actions pipeline builder","github actions","pipeline builder","github","builder","pipeline","testing","ci","workflows","run","release","write","edit","git"]