State Management 2026: Was du wann einsetzen solltest
Praktischer Leitfaden zur Wahl des State-Managements in modernen React-Apps – von useState über Zustand bis React Query – und die Gründe dahinter.

Das State-Management-Paradoxon
Noch nie gab es so viele Möglichkeiten für State Management wie heute – und trotzdem kommt die Mehrzahl der Anwendungen mit einer Handvoll davon aus. Der häufigste Fehler, den ich sehe: zu einer globalen State-Library zu greifen, bevor einfachere Lösungen überhaupt ausprobiert wurden.
Das folgende Entscheidungsschema habe ich über die Arbeit an Dutzenden React-Anwendungen hinweg verfeinert.
Die Hierarchie des State
Bevor du ein Tool auswählst, ordne zunächst ein, was du überhaupt verwaltest:
| Kategorie | Beispiele | Beste Wahl |
|---|---|---|
| Lokaler UI-State | Modal geöffnet, Formulareingabe, Hover | useState / useReducer |
| Geteilter UI-State | ausgewählter Tab, Sidebar geöffnet | Context oder Zustand |
| Server-Cache | Nutzerdaten, Produktliste | TanStack Query / SWR |
| URL-State | Filter, Paginierung, Suche | URL-Parameter / nuqs |
| Persistente Einstellungen | Theme, Sprache | localStorage + context |
Die meisten „Wir brauchen Redux“-Entscheidungen sind in Wirklichkeit „Wir brauchen TanStack Query“-Entscheidungen.
Ebene 1: lokaler State — hier fängst du an
Starte immer mit lokalem State. Der überwiegende Teil des UI-State gehört zu genau einer Komponente.
// ✅ This doesn't need to be global
function Modal({ onClose }: { onClose: () => void }) {
const [step, setStep] = useState<"details" | "confirm" | "done">("details");
const [formData, setFormData] = useState({ name: "", email: "" });
return (
<dialog>
{step === "details" && (
<DetailsForm data={formData} onChange={setFormData} onNext={() => setStep("confirm")} />
)}
{step === "confirm" && (
<ConfirmStep data={formData} onBack={() => setStep("details")} onSubmit={handleSubmit} />
)}
</dialog>
);
}Bei komplexem lokalem State mit mehreren Teilwerten führt useReducer zu saubererem Code als mehrere einzelne useState-Aufrufe.
Ebene 2: Server-State — TanStack Query
Wenn du API-Daten in einem globalen Store ablegst, arbeitest du gegen die Natur der Sache. Server-State hat eigene Besonderheiten – er ist asynchron, kann veralten und durch andere Operationen invalidiert werden. Genau dafür ist TanStack Query gebaut.
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
function UserProfile({ userId }: { userId: string }) {
const queryClient = useQueryClient();
// Automatic loading, error, and staleness handling
const { data: user, isLoading } = useQuery({
queryKey: ["users", userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // Fresh for 5 minutes
});
const updateUser = useMutation({
mutationFn: (data: Partial<User>) => patchUser(userId, data),
onSuccess: () => {
// Invalidate and refetch after mutation
queryClient.invalidateQueries({ queryKey: ["users", userId] });
},
});
if (isLoading) return <Skeleton />;
return (
<form onSubmit={(e) => {
e.preventDefault();
updateUser.mutate({ name: e.target.name.value });
}}>
<input name="name" defaultValue={user.name} />
<button type="submit" disabled={updateUser.isPending}>
{updateUser.isPending ? "Saving..." : "Save"}
</button>
</form>
);
}Das ersetzt manuell verwalteten Ladezustand, Fehlerzustand, Caching-Logik, Refetching im Hintergrund und optimistische Updates – alles, was sonst in deinem globalen Store landen würde.
Ebene 3: URL-State — unterschätzt und selten genutzt
Filter, Paginierung, Sortierung und Suchanfragen gehören in die URL. Das ist kostenloser State, der:
- Seiten-Neuladen übersteht
- sich per Link teilen lässt
- mit Vor- und Zurück-Buttons des Browsers funktioniert
- von Suchmaschinen indexiert werden kann
import { useQueryStates } from "nuqs";
function ProductList() {
const [params, setParams] = useQueryStates({
page: parseAsInteger.withDefault(1),
sort: parseAsString.withDefault("newest"),
category: parseAsString.withDefault(""),
minPrice: parseAsInteger.withDefault(0),
});
const { data } = useQuery({
queryKey: ["products", params],
queryFn: () => fetchProducts(params),
});
return (
<div>
<FilterBar filters={params} onChange={setParams} />
<ProductGrid products={data?.items} />
<Pagination
page={params.page}
total={data?.total}
onChange={(page) => setParams({ page })}
/>
</div>
);
}Ebene 4: Client-State — Zustand, wenn du ihn brauchst
Wenn du State hast, der:
- über viele, weit voneinander entfernte Komponenten hinweg geteilt wird
- keine Serverdaten enthält
- sich nicht ohne Weiteres in der Nähe seiner Verwendung unterbringen lässt
Zustand ist die sauberste Lösung – minimales Boilerplate, keine Provider nötig, exzellenter TypeScript-Support.
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface CartStore {
items: CartItem[];
addItem: (product: Product, quantity: number) => void;
removeItem: (productId: string) => void;
clearCart: () => void;
total: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (product, quantity) =>
set((state) => {
const existing = state.items.find((i) => i.productId === product.id);
if (existing) {
return {
items: state.items.map((i) =>
i.productId === product.id
? { ...i, quantity: i.quantity + quantity }
: i,
),
};
}
return {
items: [
...state.items,
{ productId: product.id, product, quantity },
],
};
}),
removeItem: (productId) =>
set((state) => ({
items: state.items.filter((i) => i.productId !== productId),
})),
clearCart: () => set({ items: [] }),
total: () =>
get().items.reduce(
(sum, item) => sum + item.product.price * item.quantity,
0,
),
}),
{ name: "cart-storage" }, // Auto-persisted to localStorage
),
);Der Entscheidungsbaum
New state to manage?
│
├─ Is it UI state for one component?
│ └─ YES → useState / useReducer
│
├─ Is it data from a server?
│ └─ YES → TanStack Query
│
├─ Should it be in the URL?
│ └─ YES → URL params (nuqs)
│
├─ Is it shared across many components?
│ └─ YES → Zustand
│
└─ Is it a user preference that should persist?
└─ YES → localStorage + small Zustand slice
Das beste State-Management-System ist eines, das du kaum bemerkst. Wenn du ständig mit deiner State-Library kämpfst, übernimmt sie wahrscheinlich zu viele Aufgaben, die andere Ebenen besser erledigen könnten.


