Was macht zustand-state-management?
Erstelle typsichere globale Zustände in React mit Zustand. Unterstützt TypeScript, Persist-Middleware, Devtools, Slice-Pattern und Next.js SSR mit Hydrations-Handling. Verhindert 6 dokumentierte Fehler. Verwende es beim Einrichten von React-Status, Migration von Redux/Context oder bei der Fehlersuche bei Hydrationsfehlern, TypeScript-Inferenz, unendlichen Render-Schleifen oder Persist-Race-Conditions.
Einrichtung
npm install zustand
Typsicherer Basis-Store
import { create } from 'zustand'
interface BearStore {
bears: number
increase: () => void
reset: () => void
}
export const useBearStore = create<BearStore>()((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
reset: () => set({ bears: 0 }),
}))
Wichtig: Rufe create<T>() mit doppelten Klammern auf. Fehlt das leere () nach dem Generikum (create<T> statt create<T>()()), kann TypeScript den Rückgabetyp nicht korrekt ableiten und es entstehen unsichtbare Typfehler.
Verwendung in einer Komponente
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} bears</h1>
}
function Controls() {
const increase = useBearStore((state) => state.increase)
return <button onClick={increase}>one up</button>
}
Selektoren sind wichtig: Indem du einen Selektor an useBearStore übergibst, verhinderst du unnötige Neurenderings. Wenn die Komponente nur bears benötigt, rendert sie auch nur neu, wenn sich bears ändert.
Persist-Middleware (localStorage / AsyncStorage)
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface BearStore {
bears: number
addBear: () => void
}
export const useBearStore = create<BearStore>()(
persist(
(set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
}),
{
name: 'bear-storage',
},
),
)
Hinweis: Bei Verwendung von Persist wird der Status beim ersten Laden aus dem Speicher wiederhergestellt. Der zurückgegebene Store hat eine _persist-Eigenschaft, mit der du den Wiederherstellungsstatus überwachen kannst.
Persist + Hydration in Next.js (SSR)
import { useEffect, useState } from 'react'
// Erstelle den Store wie oben mit persist
function HydratedBearCounter() {
const [hydrated, setHydrated] = useState(false)
useEffect(() => {
setHydrated(true)
}, [])
// 🚫 Nicht direkt `useBearStore` aufrufen – das würde zu Hydrationsfehlern führen
const bears = useBearStore((state) => state.bears)
if (!hydrated) {
return null // oder einen Lade-Platzhalter rendern
}
return <h1>{bears} bears</h1>
}
Warum: Bei SSR rendert der Server den initialen Status (0). Der Client rendert dann den persistenten Wert. Dies verursacht einen Hydrationsfehler. Die Lösung: Warte auf hydrated und rendere erst dann den persistenten Wert.
Devtools-Unterstützung
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
const useBearStore = create<BearStore>()(
devtools(
(set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
}),
{ name: 'bear-store' },
),
)
Erklärung: Devtools ermöglichen dir, alle Zustandsänderungen in den React DevTools (Browser-Erweiterung) zu verfolgen. Der optionale name-Parameter wird im Devtools-Panel als Label angezeigt.
Slice-Pattern (große Stores organisieren)
import { create } from 'zustand'
interface BearSlice {
bears: number
addBear: () => void
}
interface FishSlice {
fishes: number
addFish: () => void
}
// Erstelle die Slice-Funktionen separat
const createBearSlice: StateCreator<BearSlice & FishSlice, [], [], BearSlice> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
const createFishSlice: StateCreator<BearSlice & FishSlice, [], [], FishSlice> = (set) => ({
fishes: 0,
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})
// Kombiniere die Slices zu einem Store
export const useBoundStore = create<BearSlice & FishSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))
Vorteil: Wenn dein Store zu groß wird, kannst du ihn in logische Slices aufteilen. Jeder Slice kann in einer eigenen Datei gepflegt und in der Store-Zusammenstellung importiert werden.
6 dokumentierte Fehler
| # | Fehler | Ursache | Lösung |
|---|---|---|---|
| 1 | Typrückschluss schlägt fehl | create<T>() ohne leeres () verwendet | Immer create<T>()() verwenden |
| 2 | Hydrationsfehler in Next.js | persist-Store direkt in einer SSR-Komponente verwendet | Hydrationszustand mit _persist prüfen |
| 3 | Unendliche Render-Schleife | Aktualisierung von set in einem Effekt ohne Abhängigkeiten | useShallow oder stabilen Selektor verwenden |
| 4 | Persist-Race-Condition | Mehrere persist-Instanzen lesen/schreiben gleichzeitig | partialize verwenden oder merge konfigurieren |
| 5 | TypeScript-Fehler beim Slice-Pattern | StateCreator hat falsche generische Typen | Die richtige StateCreator<FullState, [], [], SliceType>-Syntax verwenden |
| 6 | veraltetes immer-Middleware | immer() wird als Middleware verwendet (vor v4.4) | immer als transformer mit mutate verwenden oder produce direkt aufrufen |
Auswahl der Middleware-Kombinationen
// Persist + Devtools + Immer (optional)
import { create } from 'zustand'
import { persist, devtools } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
const useBearStore = create<BearStore>()(
devtools(
persist(
immer((set) => ({
bears: 0,
addBear: () => set((state) => { state.bears++ }), // direkte Mutation möglich
})),
{ name: 'bear-storage' },
),
{ name: 'bear-store' },
),
)
Hinweis: Die Reihenfolge der Middlewares ist wichtig: Die äußerste Middleware wird zuerst aufgerufen. Typischerweise ist devtools die äußerste, dann persist, dann immer (oder keine).
Weitere bewährte Praktiken
- Selektoren immer außerhalb der Komponente definieren, um Referenzgleichheit zu gewährleisten.
useShallowverwenden, wenn du mehrere Werte aus dem Store benötigst.- Keine asynchronen Aktionen in
setverwenden – rufesetinnerhalb eines asynchronen Blocks auf. - TypeScript: Den Store generieren, nicht den
useStore-Hook – exportiere den gesamtencreate()-Aufruf.