Building a Multi-Page SPA with React RouterEntry 14 of 20
Dynamic Loaders and useRouteLoaderData
Loaders receive { params, request } for dynamic routes. Giving a route an id lets its children read its loader data with useRouteLoaderData(id).
Notes
paramsin the loader is the same objectuseParams()returns.- Detail and edit pages often need the same data. Move the loader to a parent route without an element, and have both children read it.
useLoaderData()only returns the closest route's own loader data, so children useuseRouteLoaderData('event-detail')to reach the parent's.- Throw
data(..., { status: 404 })when the item doesn't exist.
What / Why / When
- What
Param-aware loaders and shared parent data.
- Why
Fetch once, use in several child routes, no prop drilling.
- When
Detail + edit pages, or any nested routes needing the same record.
Code compare
// services/events.tsexport async function getEvent(id: string): Promise<EventItem | null> { const response = await fetch(`${API}/events/${id}`); if (response.status === 404) return null; if (!response.ok) throw new Error("Could not fetch event details."); const data = await response.json(); return data.event;}With paramsInheritanceStrategy: 'always', children inherit the parent's params and resolved data, which then bind to their inputs.
// app.config.tsprovideRouter( routes, withComponentInputBinding(), withRouterConfig({ paramsInheritanceStrategy: "always" }),);Quick reference
| Task | React | Angular |
|---|---|---|
| Read params in loader/resolver | ({ params }) => params.eventId | (route) => route.paramMap.get('eventId') |
| Share parent data | id + useRouteLoaderData(id) | paramsInheritanceStrategy: 'always' + input |
| Not found | throw data(..., { status: 404 }) | RedirectCommand to not-found |