Zustand in React Native: State Management Guide 2026

Zustand ist die schlanke Alternative zu Redux für React Native. So setzt du typsichere Stores auf, persistierst State mit MMKV und arbeitest mit der New Architecture in 2026.

Zustand in React Native: Guide 2026

Aktualisiert: 24. Juni 2026

Zustand ist eine minimalistische State-Management-Bibliothek für React und React Native, die mit nur etwa 1,2 KB gzipped auskommt und ohne Provider, Reducer oder Boilerplate funktioniert. In React Native eignet sich Zustand 2026 hervorragend für globalen State, weil es vollständig kompatibel mit der New Architecture (Fabric, TurboModules), React 19 und Concurrent Rendering ist und auf useSyncExternalStore aufbaut. Dieser Leitfaden zeigt, wie ich Zustand in einem Expo-Projekt einsetze, typsicheren Code schreibe und State mit AsyncStorage oder MMKV persistiere.

  • Zustand 5.x benötigt React 18.2 oder höher und nutzt intern useSyncExternalStore, vollständig kompatibel mit der React Native New Architecture und React 19.
  • Im Gegensatz zu Redux Toolkit kommt Zustand ohne <Provider>, Reducer oder Action-Konstanten aus. Ein Store wird mit einer einzigen create()-Funktion definiert.
  • Selektoren mit flachem Vergleich verhindern unnötige Re-Renders und sind in React-Native-Listen mit FlashList Pflicht, nicht Empfehlung.
  • Persistenz funktioniert über das offizielle persist-Middleware mit AsyncStorage oder, deutlich schneller, mit MMKV als synchroner Storage-Adapter.
  • Mit dem devtools-Middleware lässt sich der Store an die Redux DevTools anschließen, auch in React Native mit React Native Debugger.
  • TypeScript-Inferenz funktioniert in Zustand 5 zuverlässig, wenn Sie das Store-Interface als Generic an create<State>()() übergeben (doppelter Aufruf).

Was ist Zustand und wie funktioniert es?

Zustand (deutsch für „state") ist eine State-Management-Bibliothek, die von Daishi Kato und dem Team rund um Jotai und Valtio gepflegt wird. Sie wurde ursprünglich für React entwickelt, läuft aber unverändert in React Native, weil sie keinerlei DOM-spezifische APIs nutzt. Der Kern besteht aus einer einzigen Funktion namens create(), die einen Hook zurückgibt, mit dem Komponenten den Store lesen und mutieren können.

Technisch basiert Zustand auf einem klassischen Publish-Subscribe-Muster. Jeder Store hält einen aktuellen State, eine Liste von Listenern und drei interne Methoden: getState, setState und subscribe. Beim Aufruf des erzeugten Hooks registriert sich die Komponente intern über useSyncExternalStore aus React 18, was zwei wichtige Konsequenzen hat. Updates sind tearing-frei, und der Store funktioniert ohne Anpassung mit Concurrent Rendering, Suspense und der React Native New Architecture.

Anders als Redux ist Zustand mutationsfreundlich. Sie können entweder neue Objekte zurückgeben oder mit dem immer-Middleware direkt mutieren, beides ist erlaubt. Es gibt keine Action-Konstanten, keine Switch-Statements und keinen combineReducers-Aufruf. Ein vollständiger Counter-Store passt in fünf Zeilen, und derselbe Store funktioniert in Web und Mobile identisch. Genau das hat mich vor zwei Jahren überzeugt, eine bestehende Redux-Codebasis Schritt für Schritt zu migrieren, und ich habe es seither nicht bereut.

Zustand vs. Redux Toolkit: Vergleich für React Native

Die häufigste Frage in meinem Posteingang lautet: „Lohnt sich Zustand, wenn das Team bereits Redux Toolkit kennt?" Die ehrliche Antwort ist „meistens ja", denn Zustand reduziert den Boilerplate-Aufwand pro Slice um etwa 60 Prozent und benötigt keinen Provider im Component-Tree. Die folgende Tabelle vergleicht die beiden Bibliotheken auf den Achsen, die in mobilen Apps wirklich zählen.

KriteriumZustand 5Redux Toolkit 2
Bundle-Größe (gzipped)~1,2 KB~13 KB (mit React-Redux)
Boilerplate für einen Slice5 bis 10 Zeilen20 bis 40 Zeilen
Provider erforderlichNeinJa (<Provider>)
TypeScript-InferenzSehr gut (mit Curry-Syntax)Sehr gut (createSlice)
Devtools-SupportÜber MiddlewareEingebaut
Async-LogikIn Actions, ohne Thunk-SetupcreateAsyncThunk oder RTK Query
Persistenzpersist-Middleware (offiziell)redux-persist (drittanbieter)
New Architecture-kompatibelJaJa

Redux Toolkit gewinnt, wenn Sie bereits eine ausgereifte Normalisierungs-Strategie mit createEntityAdapter haben oder wenn RTK Query für das Caching von Server-State eingesetzt wird. Für alles andere (lokale UI-States, Auth-Tokens, Theming, ein Warenkorb) ist Zustand schlanker und schneller einzuführen. Wer Server-State separat hält, etwa mit TanStack Query, kommt mit Zustand für Client-State meist vollständig aus.

Zustand in Expo und React Native installieren

Die Installation ist denkbar einfach. Mit Expo SDK 53 oder neuer reicht ein einziger Befehl:

npx expo install zustand
# oder für ein nacktes React Native-Projekt:
npm install zustand

Für Persistenz benötigen Sie zusätzlich einen Storage-Adapter. Mit AsyncStorage:

npx expo install @react-native-async-storage/async-storage

Wer maximale Performance möchte (etwa für Apps, die beim App-Start hunderte Einträge laden), sollte stattdessen MMKV verwenden. MMKV arbeitet synchron und blockiert damit den ersten Render nicht:

npx expo install react-native-mmkv

MMKV erfordert die New Architecture (Fabric) oder mindestens Expo SDK 50+. Wenn Ihr Projekt noch auf dem alten Bridge-System läuft, sollten Sie zuerst die Migration zur New Architecture abschließen. Wir haben das im FlashList v2 Leitfaden für die New Architecture detailliert beschrieben.

Ihren ersten Store mit TypeScript erstellen

Ein typsicherer Store besteht aus einem Interface, das State und Actions beschreibt, und dem Aufruf von create mit der sogenannten Curry-Syntax (zwei Klammerpaare). Diese Schreibweise ist nötig, damit TypeScript Generics korrekt auflöst. Eine Eigenheit, die in der offiziellen Dokumentation als „middleware typing" bekannt ist und beim ersten Kontakt verwirrend wirkt.

// stores/useCounterStore.ts
import { create } from 'zustand'

interface CounterState {
  count: number
  increment: () => void
  decrement: () => void
  reset: () => void
  incrementBy: (n: number) => void
}

export const useCounterStore = create<CounterState>()((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
  incrementBy: (n) => set((state) => ({ count: state.count + n })),
}))

In einer Komponente verwenden Sie den Store wie jeden anderen Hook. Zwei Aufrufe sind üblich: einer für den State, einer für die Action. So bleibt die Komponente unabhängig vom Aufbau des Stores:

// screens/CounterScreen.tsx
import { View, Text, Button } from 'react-native'
import { useCounterStore } from '../stores/useCounterStore'

export default function CounterScreen() {
  const count = useCounterStore((s) => s.count)
  const increment = useCounterStore((s) => s.increment)

  return (
    <View>
      <Text>Aktueller Zählerstand: {count}</Text>
      <Button title="+1" onPress={increment} />
    </View>
  )
}

Beachten Sie, dass jede Komponente nur die Felder selektiert, die sie wirklich braucht. Dieses Muster ist der Schlüssel zur Performance und wird im nächsten Abschnitt vertieft.

Selektoren und Re-Render-Optimierung

Zustand führt einen Re-Render aus, wenn der Selektor-Rückgabewert sich per Object.is ändert. Wer den ganzen Store auf einmal selektiert, zwingt jede Komponente zu einem Re-Render bei jeder beliebigen Änderung. Das ist der häufigste Performance-Fehler, den ich in größeren React-Native-Apps sehe, und er versteckt sich gerne in scheinbar harmlosen Custom Hooks.

// SCHLECHT: die Komponente rendert bei jeder State-Aenderung neu
const { count, increment } = useCounterStore()

// GUT: nur bei Aenderung von count
const count = useCounterStore((s) => s.count)
const increment = useCounterStore((s) => s.increment)

Wenn Sie mehrere Werte gleichzeitig benötigen, gibt der Selektor ein neues Objekt zurück, und der flache Vergleich schlägt fehl. Die Lösung in Zustand 5 ist die useShallow-Hilfsfunktion:

import { useShallow } from 'zustand/react/shallow'

const { count, increment } = useCounterStore(
  useShallow((s) => ({ count: s.count, increment: s.increment }))
)

State persistieren mit AsyncStorage und MMKV

Für Auth-Tokens, Theme-Einstellungen oder Onboarding-Flags möchten Sie den State über App-Neustarts hinweg behalten. Das offizielle persist-Middleware übernimmt das mit minimaler Konfiguration. So sieht ein Auth-Store mit AsyncStorage aus:

// stores/useAuthStore.ts
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import AsyncStorage from '@react-native-async-storage/async-storage'

interface AuthState {
  token: string | null
  user: { id: string; email: string } | null
  setToken: (token: string) => void
  logout: () => void
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      user: null,
      setToken: (token) => set({ token }),
      logout: () => set({ token: null, user: null }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({ token: state.token }),
    }
  )
)

Das partialize-Feld ist wichtig. Es definiert, welche Felder persistiert werden. So bleibt das User-Objekt im RAM, aber das Token überlebt einen App-Restart. Für sichere Token sollten Sie statt AsyncStorage allerdings expo-secure-store verwenden. Dieselbe API funktioniert, weil der Storage-Adapter nur getItem, setItem und removeItem erwartet.

Wer MMKV vorzieht, gewinnt synchrone Reads. Der State ist beim ersten Render bereits hydratisiert, und es gibt keinen Flicker. Die offizielle react-native-mmkv-Dokumentation zeigt einen kompletten Adapter, der drop-in für AsyncStorage einsetzbar ist.

Asynchrone Actions und API-Aufrufe

Anders als Redux benötigen Sie für Async-Actions keinen Thunk-Middleware. Eine Action ist einfach eine Funktion, die async sein darf und intern set aufruft. Das folgende Beispiel lädt einen User-Datensatz und behandelt Lade- und Fehlerzustände:

interface UserState {
  user: User | null
  loading: boolean
  error: string | null
  fetchUser: (id: string) => Promise<void>
}

export const useUserStore = create<UserState>()((set) => ({
  user: null,
  loading: false,
  error: null,
  fetchUser: async (id) => {
    set({ loading: true, error: null })
    try {
      const response = await fetch(`https://api.example.com/users/${id}`)
      if (!response.ok) throw new Error('Netzwerkfehler')
      const user = await response.json()
      set({ user, loading: false })
    } catch (e) {
      set({ error: (e as Error).message, loading: false })
    }
  },
}))

Für komplexen Server-State mit Caching, Refetching und Optimistic Updates ist TanStack Query die bessere Wahl. Eine bewährte Architektur in 2026 sieht so aus: TanStack Query für alles, was vom Server kommt; Zustand für alles, was nur im Client lebt (UI-State, Auth-Token, lokale Filter). Diese Trennung verhindert, dass Sie API-Caches manuell synchronisieren müssen. Ehrlich gesagt habe ich diese Lektion auf die harte Tour gelernt, als ich anfangs versucht habe, beides in einem Zustand-Store zu halten.

Zustand mit der React Native New Architecture

Mit React Native 0.79+ und Expo SDK 53+ ist die New Architecture (Fabric, TurboModules, Hermes mit React 19) standardmäßig aktiviert. Zustand ist hier seit Version 4.4 vollständig kompatibel, weil es intern useSyncExternalStore nutzt, denselben Hook, den React 19 für externe Stores empfiehlt. Es gibt drei Punkte, die ich in der Praxis beachte.

Erstens: Concurrent Rendering kann denselben Selektor mehrfach in einem Frame aufrufen. Selektoren müssen daher rein sein, also keine Date-Berechnungen, keine Math.random-Aufrufe, keine Logging-Seiteneffekte. Zweitens: Wer Zustand in einem React-Server-Component-Setup einsetzt (etwa Expo Router mit RSC-Mode), muss den Store strikt clientseitig deklarieren, entweder im selben File mit 'use client' oder in einem dedizierten Stores-Verzeichnis. Drittens: Persistenz mit asynchronen Storage-Adaptern führt zu einem kurzen Zeitraum, in dem der Store noch nicht hydratisiert ist. Nutzen Sie useAuthStore.persist.hasHydrated(), um in einem Splash-Screen darauf zu warten. Genau diesen Bug habe ich in meinem letzten Projekt verschlafen, und der Auth-Flow blitzte für einen halben Frame als „eingeloggt" auf, bevor er korrigiert wurde.

Wenn Sie auf der New Architecture mit Navigation arbeiten, lohnt sich auch ein Blick in unseren Leitfaden zu Expo Router v5 und Authentifizierung mit Protected Routes, der den hier vorgestellten Auth-Store in ein konkretes Routing-Setup einbettet.

Debugging mit den Redux DevTools

Auch wenn Zustand kein Redux ist, lässt sich der gesamte State-Verlauf über die Redux DevTools einsehen. Das ist in React Native besonders wertvoll, weil die klassische Flipper-Integration ab React Native 0.74 nicht mehr offiziell unterstützt wird. Das devtools-Middleware öffnet eine Verbindung zur Browser-Extension oder zum eigenständigen React Native Debugger:

import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

export const useCartStore = create<CartState>()(
  devtools(
    (set) => ({
      items: [],
      addItem: (item) => set(
        (state) => ({ items: [...state.items, item] }),
        false,
        'cart/addItem'
      ),
    }),
    { name: 'cart-store' }
  )
)

Der dritte Parameter von set ist optional und beschreibt die Action im DevTools-Log. Für Production-Builds sollten Sie das Middleware via process.env.NODE_ENV deaktivieren, sonst hängt die App im Release-Build an einer nicht erreichbaren WebSocket-Verbindung.

Häufige Fehler und wie Sie sie vermeiden

In Code-Reviews sehe ich immer wieder dieselben drei Stolpersteine. Erstens: Den ganzen Store selektieren. Wie oben gezeigt, führt das zu massiven Re-Render-Kaskaden, insbesondere in Listen. Zweitens: Veraltete Closures in Callbacks. Wenn Sie eine Action außerhalb der Komponente abrufen wollen, nutzen Sie useStore.getState() statt eines im Render geschlossenen Werts. Drittens: Den Store-Hook im Render aufrufen, aber das Ergebnis nie verwenden. TypeScript warnt nicht, aber Sie zahlen den Subscriber-Overhead trotzdem.

Ein vierter, subtiler Fehler betrifft Persistenz. Wenn Sie das Datenschema später ändern (zum Beispiel ein Feld umbenennen), bekommen Bestandsnutzer beim Restart den alten persistierten State zurück, der nicht mehr passt. Setzen Sie deshalb von Anfang an version und einen migrate-Callback auf das persist-Config-Objekt. Die einzelnen Schritte sind in der offiziellen Zustand-Persist-Dokumentation beschrieben.

Häufig gestellte Fragen

Ist Zustand besser als Redux für React Native?

Für die meisten React-Native-Apps in 2026 ja. Zustand benötigt etwa 70 Prozent weniger Code pro Feature und ist deutlich kleiner im Bundle. Redux Toolkit ist nur dann überlegen, wenn Sie RTK Query für Server-State oder createEntityAdapter für normalisierte Datenstrukturen brauchen.

Funktioniert Zustand mit der React Native New Architecture?

Ja, vollständig. Zustand 5 nutzt useSyncExternalStore und ist mit Fabric, TurboModules, React 19 und Concurrent Rendering kompatibel. Es gibt keine Migrationsschritte beim Wechsel auf die New Architecture.

Wie persistiere ich Zustand-State in React Native?

Verwenden Sie das offizielle persist-Middleware in Kombination mit AsyncStorage, expo-secure-store oder react-native-mmkv. MMKV ist die schnellste Option, weil sie synchron arbeitet und den ersten Render nicht blockiert.

Brauche ich einen Provider für Zustand?

Nein. Zustand-Stores sind globale Module und werden ohne <Provider> verwendet. Wenn Sie pro Screen einen isolierten Store brauchen (etwa für Wizard-Flows), können Sie optional die Context-API mit einer Store-Factory kombinieren.

Kann ich Zustand mit TanStack Query kombinieren?

Das ist sogar die empfohlene Architektur. TanStack Query verwaltet Server-State mit Caching und Refetching, während Zustand reinen Client-State wie UI-Flags, Auth-Tokens und lokale Filter hält. So vermeiden Sie doppelte Caches.

Über den Autor Editorial Team

Our team of expert writers and editors.