# Step 6 — Routing with react-router-dom + Data Fetching with TanStack Query

Part 1's last step covers two libraries that always show up together: [react-router-dom](https://reactrouter.com) turns a URL into a component, and [TanStack Query v5](https://tanstack.com/query) turns a network request into cached, reactive data. This very tutorial uses both — the URL you're reading this on (`/guides/react-ts-as-frontend/router-and-query`) is parsed by react-router-dom, and the markdown content was fetched (well — statically bundled, see Step 3) the same way real API data is fetched elsewhere in the app.

## Routes as data, not JSX trees

`App.tsx` declares the docs routes plainly:

```tsx
<Route path="/docs" element={<DocsLayout />}>
  <Route index element={<DocsIndex />} />
  <Route path=":section" element={<DocsContent />} />
  <Route path=":section/:page" element={<DocsContent />} />
</Route>
```

`:section` and `:page` are **route params** — placeholders that match any URL segment. `DocsContent` reads them back out with `useParams()`.

## `useParams` in a real page: `TeamMembers.tsx`

```tsx
import { useParams } from 'react-router-dom';

export function TeamMembers() {
  const { teamId: teamSlugOrId } = useParams<{ teamId: string }>();

  const { team, members: teamMembers, isLoading: isTeamLoading } =
    useTeamDashboard(teamSlugOrId);
  // ...
}
```

The route for this page is declared somewhere as `path="/teams/:teamId/members"`. Whatever segment appears where `:teamId` is — a slug or a UUID — `useParams<{ teamId: string }>()` returns it as `teamSlugOrId`. The generic `<{ teamId: string }>` tells TypeScript what shape to expect back, so `teamSlugOrId` is typed as `string | undefined`, not `any`.

Notice `teamSlugOrId` then flows straight into a **custom hook**, `useTeamDashboard` — this is the seam between routing (where am I?) and data fetching (what do I need to show?).

## TanStack Query: the shape every custom hook follows

You'll trace this in full with real code in Step 11, but the shape is worth previewing now. A TanStack Query hook wraps `useQuery` (for reads) and `useMutation` (for writes) around an API call:

```ts
const tagsQuery = useQuery({
  queryKey: ['tags', teamId],
  queryFn: () => tagsApi.list({ team_id: teamId }),
  enabled: !!teamId,
  staleTime: 5 * 60 * 1000,
});
```

- **`queryKey`** is a cache key — `['tags', teamId]` means "the tags list for this specific team." Change `teamId` and TanStack Query treats it as a different cache entry automatically.
- **`queryFn`** is the actual fetch — any `async` function returning the data.
- **`enabled: !!teamId`** skips fetching entirely until `teamId` exists — useful when a param arrives from the route a tick after the component first renders.
- **`staleTime`** controls how long cached data is considered fresh before a background refetch — no manual cache invalidation loops.

The payoff: any two components that call `useTags(teamId)` for the *same* `teamId` share one in-flight request and one cache entry, with loading and error states handled for you (`tagsQuery.isLoading`, `tagsQuery.error`) instead of hand-rolled `useEffect` + `useState` fetching.

## Checkpoint

You've now covered every piece Part 1 set out to teach: TypeScript unions and props (Step 2), Vite + project structure (Step 3), TailwindCSS + shadcn/ui composition (Step 4), Zustand for client state (Step 5), and routing + query fundamentals (this step).

Continue to **Step 7 — The Database Layer: TagRepository in Rust**, where Part 2 begins: a single real feature, traced from a Postgres row to a rendered dialog.
