Dynamische Routen & Parameter
Die Todo-Liste zeigt alle Einträge. Ein Klick auf einen Eintrag soll eine Detailansicht öffnen — mit einer eigenen URL pro Todo: /todos/1, /todos/42. Das ist eine dynamische Route: der Pfad hat einen variablen Teil, den React Router als Parameter bereitstellt.
Route mit Parameter definieren
Abschnitt betitelt „Route mit Parameter definieren“Ein Doppelpunkt im Pfad markiert einen Parameter:
import TodoDetail from './TodoDetail'
const router = createBrowserRouter([ { path: '/', element: <App />, children: [ { index: true, element: <TodoListe /> }, { path: 'aktiv', element: <TodoAktiv /> }, { path: 'erledigt', element: <TodoErledigt /> }, { path: 'todos/:id', element: <TodoDetail /> }, ], },]):id ist ein Platzhalter — React Router matcht /todos/1, /todos/42 und jeden anderen Wert an dieser Stelle und macht ihn als id verfügbar.
useParams: Parameter lesen
Abschnitt betitelt „useParams: Parameter lesen“import { useParams, Link } from 'react-router'import { useQuery } from '@tanstack/react-query'
type Todo = { id: number; title: string; completed: boolean; userId: number }
async function fetchTodo(id: number): Promise<Todo> { const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`) if (!res.ok) throw new Error(`HTTP-Fehler: ${res.status}`) return res.json()}
export default function TodoDetail() { const { id } = useParams<{ id: string }>() const numericId = parseInt(id!, 10)
const { data: todo, isLoading, isError } = useQuery<Todo>({ queryKey: ['todo', numericId], queryFn: () => fetchTodo(numericId), })
if (isLoading) return <p>Lade...</p> if (isError) return <p>Fehler beim Laden.</p> if (!todo) return null
return ( <div> <Link to="/">← Zurück</Link> <h2>{todo.title}</h2> <p>Status: {todo.completed ? 'Erledigt' : 'Offen'}</p> <p>Nutzer: {todo.userId}</p> </div> )}useParams<{ id: string }>() gibt { id: string | undefined } zurück. Das ! nach id teilt TypeScript mit, dass der Wert hier vorhanden ist — was stimmt, solange die Komponente nur innerhalb der :id-Route verwendet wird.
Link zur Detailansicht
Abschnitt betitelt „Link zur Detailansicht“In der Liste verlinkt jeder Eintrag auf seine Detailroute:
export default function TodoListe() { const { data: todos, isLoading } = useQuery<Todo[]>({ queryKey: ['todos'], queryFn: fetchTodos, })
if (isLoading) return <p>Lade...</p>
return ( <ul> {todos!.map(todo => ( <li key={todo.id}> <Link to={`/todos/${todo.id}`}>{todo.title}</Link> </li> ))} </ul> )}Not-Found: 404 behandeln
Abschnitt betitelt „Not-Found: 404 behandeln“Wenn jemand /todos/99999 aufruft und die API einen 404 zurückgibt, wirft fetchTodo einen Fehler — isError wird true und die Fehlermeldung wird angezeigt. Das ist ausreichend für viele Fälle.
Für einen dedizierten 404-Catch auf Router-Ebene gibt es errorElement:
const router = createBrowserRouter([ { path: '/', element: <App />, errorElement: <NichtGefunden />, children: [ // ... ], },])import { Link, useRouteError, isRouteErrorResponse } from 'react-router'
export default function NichtGefunden() { const error = useRouteError()
return ( <div> <h2>Seite nicht gefunden</h2> {isRouteErrorResponse(error) && <p>Status: {error.status}</p>} <Link to="/">Zur Startseite</Link> </div> )}errorElement fängt zwei Dinge: Fehler, die in Route-Komponenten geworfen werden, und Navigationen zu Pfaden, die keiner Route entsprechen.
TypeScript
Abschnitt betitelt „TypeScript“useParams gibt string | undefined für jeden Parameter zurück — nicht string. Drei Strategien:
const { id } = useParams<{ id: string }>()
// 1. Early Exit — sauberste Lösungif (!id) return nullconst numericId = parseInt(id, 10)
// 2. Non-null assertion — wenn sicher, dass die Route den Param immer hatconst numericId = parseInt(id!, 10)
// 3. Fallbackconst numericId = parseInt(id ?? '0', 10)Der Early Exit ist bevorzugt: er macht den unmöglichen Fall explizit, ohne TypeScript zu überlisten.