Zum Inhalt springen

Loader: Daten mit der Route laden

Bisher laden unsere Komponenten ihre Daten selbst — mit useQuery nach dem ersten Render. Das bedeutet: Erst rendert die Komponente (mit Ladezustand), dann startet die Anfrage, dann rendert sie mit den Daten. React Router bietet eine Alternative: Loader starten die Datenanfrage schon während der Navigation — bevor die Komponente überhaupt rendert.

Ein Loader ist eine normale async-Funktion, die direkt an der Route-Definition hängt:

main.tsx
import { createBrowserRouter, RouterProvider } from 'react-router'
import TodoListe, { todosLoader } from './TodoListe'
import TodoDetail, { todoLoader } from './TodoDetail'
const router = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{
index: true,
element: <TodoListe />,
loader: todosLoader,
},
{
path: 'todos/:id',
element: <TodoDetail />,
loader: todoLoader,
},
],
},
])

React Router ruft den Loader auf, sobald der Nutzer zu dieser Route navigiert — parallel zum Laden des JavaScript-Bundles der Komponente, noch vor dem ersten Render.

Der Loader lebt in derselben Datei wie die Komponente und wird mitexportiert:

TodoListe.tsx
import { useLoaderData, Link } from 'react-router'
type Todo = { id: number; title: string; completed: boolean; userId: number }
export async function todosLoader(): Promise<Todo[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=20')
if (!res.ok) throw new Error(`HTTP-Fehler: ${res.status}`)
return res.json()
}
export default function TodoListe() {
const todos = useLoaderData() as Todo[]
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<Link to={`/todos/${todo.id}`}>{todo.title}</Link>
</li>
))}
</ul>
)
}

useLoaderData() gibt zurück, was der Loader zurückgegeben hat — die Komponente rendert direkt mit Daten, ohne Ladezustand.

Für dynamische Routen bekommt der Loader ein params-Objekt mit den URL-Parametern:

TodoDetail.tsx
import { useLoaderData, Link } from 'react-router'
import type { LoaderFunctionArgs } from 'react-router'
type Todo = { id: number; title: string; completed: boolean; userId: number }
export async function todoLoader({ params }: LoaderFunctionArgs): Promise<Todo> {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${params.id}`)
if (!res.ok) throw new Error(`HTTP-Fehler: ${res.status}`)
return res.json()
}
export default function TodoDetail() {
const todo = useLoaderData() as Todo
return (
<div>
<Link to="/">← Zurück</Link>
<h2>{todo.title}</h2>
<p>Status: {todo.completed ? 'Erledigt' : 'Offen'}</p>
</div>
)
}

Fehler im Loader — zum Beispiel ein 404 — werden automatisch von errorElement aufgefangen, genau wie Routing-Fehler.

useLoaderData() gibt unknown zurück, weil React Router zur Laufzeit nicht weiß, welcher Loader für die aktuelle Route zuständig ist. Der Cast auf den erwarteten Typ ist nötig:

const todos = useLoaderData() as Todo[]
const todo = useLoaderData() as Todo

Für vollständige Typsicherheit ohne Cast bietet React Router v7 einen Code-Generator, der typisierte useLoaderData-Hooks aus der Router-Konfiguration erzeugt. Für die meisten Anwendungen reicht der Cast.

Beide Ansätze funktionieren — sie lösen aber unterschiedliche Probleme:

LoaderuseQuery
Wann startet die AnfrageWährend der NavigationNach dem ersten Render
Ladezustand in der KomponenteNein — Daten sind sofort daJa — isLoading nötig
CachingNein (pro Navigation neu)Ja — Cache, Background Refetch
RefetchingNeinJa
Geeignet fürDaten, die vor dem Render da sein müssenDaten, die nach dem Render laden dürfen
SSR / StreamingJaNein (client-only)

Loader sind die bessere Wahl, wenn die Komponente ohne Daten sinnlos ist und kein Ladezustand gezeigt werden soll — zum Beispiel für die initiale Seitenstruktur oder wenn Server-Rendering relevant ist.

useQuery ist die bessere Wahl, wenn Daten im Hintergrund aktuell gehalten werden sollen, wenn gecacht werden soll oder wenn die Komponente auch ohne Daten sinnvoll rendert — zum Beispiel mit einem Skeleton.

In der Praxis werden beide häufig kombiniert: Der Loader lädt kritische Daten für die erste Darstellung, useQuery übernimmt dynamische oder oft wechselnde Daten innerhalb der Komponente.