home / skills / sebastiaanwouters / dotagents / react-router

react-router skill

/skills/react-router

npx playbooks add skill sebastiaanwouters/dotagents --skill react-router

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
4.3 KB
---
name: react-router
description: React Router v7 Data Mode for Vite SPA with Convex backend. Triggers on routing, navigation, nested routes, protected routes, lazy loading. Use for client-side routing in Vite React apps.
---

# React Router v7 (Data Mode for Vite SPA + Convex)

Use **Data Mode** (`createBrowserRouter` + `RouterProvider`) for Vite SPAs with Convex.

> Attribution: [remix-run/react-router](https://github.com/remix-run/react-router)

## Key Principle

**Convex handles data, Router handles navigation.**

- **React Router**: routing, navigation, URL params, nested layouts, lazy loading
- **Convex**: data fetching (`useQuery`), mutations (`useMutation`), real-time updates

Don't use React Router loaders/actions with Convex.

## Installation

```bash
react-router-dom
```

_(Install with your project's package manager)_

## Basic Setup

```typescript
// main.tsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { ConvexProvider, ConvexReactClient } from "convex/react";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

const router = createBrowserRouter([
  {
    path: "/",
    Component: Layout,
    ErrorBoundary: RootError,
    children: [
      { index: true, Component: Home },
      { path: "projects", Component: Projects },
      { path: "projects/:projectId", Component: Project },
    ],
  },
]);

// HMR cleanup for Vite
if (import.meta.hot) {
  import.meta.hot.dispose(() => router.dispose());
}

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <ConvexProvider client={convex}>
      <RouterProvider router={router} fallbackElement={<p>Loading...</p>} />
    </ConvexProvider>
  </StrictMode>
);
```

## Nested Routes & Outlet

```typescript
function Layout() {
  return (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/projects">Projects</Link>
      </nav>
      <Outlet /> {/* Child routes render here */}
    </div>
  );
}
```

## URL Params + Convex Query

```typescript
function Project() {
  const { projectId } = useParams<{ projectId: string }>();
  
  const project = useQuery(
    api.projects.get,
    projectId ? { id: projectId as Id<"projects"> } : "skip"
  );

  if (project === undefined) return <p>Loading...</p>;
  if (project === null) return <p>Not found</p>;

  return <h1>{project.name}</h1>;
}
```

## Mutation + Navigate

```typescript
function CreateProject() {
  const navigate = useNavigate();
  const createProject = useMutation(api.projects.create);

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const id = await createProject({ name: formData.get("name") as string });
    navigate(`/projects/${id}`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" required />
      <button type="submit">Create</button>
    </form>
  );
}
```

## Protected Routes

```typescript
function ProtectedRoute() {
  const { isAuthenticated, isLoading } = useConvexAuth();
  const location = useLocation();

  if (isLoading) return <p>Loading...</p>;
  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  return <Outlet />;
}

// Usage in router
{
  Component: ProtectedRoute,
  children: [
    { path: "dashboard", Component: Dashboard },
  ],
}
```

## Quick Reference

| Task | API |
|------|-----|
| Navigate programmatically | `useNavigate()` |
| Get URL params | `useParams()` |
| Get/set search params | `useSearchParams()` |
| Get current location | `useLocation()` |
| Check navigation state | `useNavigation()` |
| Link with active state | `<NavLink>` |
| Render child routes | `<Outlet />` |
| Code split routes | `lazy: () => import()` |

## Critical Rules

1. **Use Convex hooks for data** - `useQuery`/`useMutation` provide real-time updates
2. **Don't use loaders/actions** - Convex handles data, Router handles navigation
3. **Handle loading states** - `useQuery` returns `undefined` while loading
4. **Dispose router in HMR** - Prevents memory leaks in Vite dev mode
5. **Wrap Router in ConvexProvider** - Convex context must be available

## Deep Dive References

- [examples.md](references/examples.md) - Complete CRUD, auth patterns, search params
- [api.md](references/api.md) - All hooks and components reference