Zustand и TanStack Query в React Native: Практическо ръководство за управление на състоянието

Пълно практическо ръководство за Zustand и TanStack Query в React Native. Кодови примери, persist middleware, оптимистични обновявания, архитектурни препоръки и типични грешки, които да избягвате.

Zustand + TanStack Query: Гайд за RN 2026

Въведение: Защо управлението на състоянието е толкова важно

Ако сте разработвали React Native приложения повече от няколко месеца, вероятно знаете това чувство — започвате с прост useState, после добавяте Context, после нещата се заплитат и изведнъж прекарвате повече време в дебъгване на пропсове отколкото в писане на реална логика.

Управлението на състоянието е един от най-критичните аспекти при разработката на мобилни приложения. Всяко приложение — от прост to-do лист до сложна електронна търговия — трябва да съхранява, обновява и синхронизира данни между компоненти, екрани и сървъри. Правилната стратегия може да направи разликата между бързо, отзивчиво приложение и такова, което се „закъсва" при всяко взаимодействие.

Redux дълго време беше стандартът. Но нека бъдем честни — писането на actions, reducers, selectors и middleware за всяка малка функционалност бързо става изморително. През 2026 комбинацията от Zustand за клиентско състояние и TanStack Query (бившият React Query) за сървърно състояние се утвърди като златния стандарт за модерни React Native приложения. И с основание.

В тази статия ще разгледаме подробно как да настроите и използвате тези две библиотеки заедно, с практически примери и реални архитектурни решения.

Ключовата идея: Разделение на клиентско и сървърно състояние

Преди да навлезем в конкретните библиотеки, трябва да разберем фундаменталната концепция зад тази архитектура — разделението на състоянието по произход.

Какво е клиентско състояние?

Клиентското състояние е информация, която съществува само в приложението и не зависи от външен източник. Примери:

  • Текущата тема (светла/тъмна)
  • Дали модална кутия е отворена или затворена
  • Предпочитания на потребителя за известия
  • Съдържание на кошницата за пазаруване (преди да го изпратите към сървъра)
  • Текущия изглед — списък или мрежа
  • Състоянието на автентикация (токен, потребителски данни)

Какво е сървърно състояние?

Сървърното състояние е данни, които идват от външен API или база данни. И тук нещата стават по-интересни, защото те имат съвсем различни характеристики:

  • Асинхронни са — трябва да се заредят преди да ги използвате
  • Могат да бъдат променени от други потребители или процеси
  • Изискват кеширане за добра производителност
  • Трябва да се обновяват периодично
  • Имат нужда от обработка на грешки и повторни опити

Примери: списък с продукти от API, потребителски профил, коментари, поръчки, известия от сървъра.

Защо разделението е важно

Когато смесвате двата типа в една библиотека (като Redux), кодът бързо става объркан. Ръчно управлявате кеширане, loading/error състояния, повторни опити при грешки, инвалидиране на кеша... Познато, нали?

С разделянето, Zustand се грижи за простото, синхронно клиентско състояние, а TanStack Query автоматично обработва цялата сложност на сървърните данни. Всеки си прави това, в което е добър.

Zustand: Лекотата на клиентското състояние

Какво е Zustand

Zustand (немска дума за „състояние") е минималистична библиотека за state management, създадена от екипа на Poimandres. С размер от едва ~1 KB (gzipped), тя предлага невероятно просто API базирано на хукове, без нужда от Provider компоненти или бойлерплейт код.

Честно казано, когато за пръв път видях Zustand, бях скептичен — толкова малко код за толкова мощна функционалност? Но числата говорят сами — над 30% годишен ръст в npm изтеглянията и изключително висока удовлетвореност сред разработчиците.

Инсталация

npm install zustand
# Или с yarn
yarn add zustand

Основен store

Нека създадем прост store за потребителски настройки:

// stores/useSettingsStore.ts
import { create } from 'zustand';

interface SettingsState {
  theme: 'light' | 'dark';
  language: string;
  notificationsEnabled: boolean;
  setTheme: (theme: 'light' | 'dark') => void;
  setLanguage: (language: string) => void;
  toggleNotifications: () => void;
}

const useSettingsStore = create<SettingsState>((set) => ({
  theme: 'light',
  language: 'bg',
  notificationsEnabled: true,
  setTheme: (theme) => set({ theme }),
  setLanguage: (language) => set({ language }),
  toggleNotifications: () =>
    set((state) => ({ notificationsEnabled: !state.notificationsEnabled })),
}));

export default useSettingsStore;

Забележете колко лаконичен е кодът. Няма actions, няма reducers, няма dispatch. Просто създавате store и го използвате.

Използване в компонент

Използването в компонент е направо елементарно — извиквате хука и готово:

// screens/SettingsScreen.tsx
import React from 'react';
import { View, Text, Switch, TouchableOpacity, StyleSheet } from 'react-native';
import useSettingsStore from '../stores/useSettingsStore';

export default function SettingsScreen() {
  const theme = useSettingsStore((state) => state.theme);
  const notificationsEnabled = useSettingsStore((state) => state.notificationsEnabled);
  const setTheme = useSettingsStore((state) => state.setTheme);
  const toggleNotifications = useSettingsStore((state) => state.toggleNotifications);

  return (
    <View style={[styles.container, theme === 'dark' && styles.darkBg]}>
      <Text style={theme === 'dark' ? styles.lightText : styles.darkText}>
        Тема: {theme === 'dark' ? 'Тъмна' : 'Светла'}
      </Text>
      <TouchableOpacity
        onPress={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
        style={styles.button}
      >
        <Text>Смени темата</Text>
      </TouchableOpacity>

      <View style={styles.row}>
        <Text style={theme === 'dark' ? styles.lightText : styles.darkText}>
          Известия
        </Text>
        <Switch value={notificationsEnabled} onValueChange={toggleNotifications} />
      </View>
    </View>
  );
}

Забележете как селектираме само конкретните стойности, от които компонентът се нуждае. Zustand автоматично оптимизира ре-рендерите — компонентът ще се обнови само когато избраните стойности се променят. Доста удобно.

Persist middleware: Запазване на състоянието при рестартиране

Едно от най-полезните допълнения на Zustand е persist middleware, което автоматично запазва състоянието в хранилище. За React Native използваме AsyncStorage:

npm install @react-native-async-storage/async-storage
// 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; name: string; email: string } | null;
  isAuthenticated: boolean;
  login: (token: string, user: AuthState['user']) => void;
  logout: () => void;
}

const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      user: null,
      isAuthenticated: false,
      login: (token, user) =>
        set({ token, user, isAuthenticated: true }),
      logout: () =>
        set({ token: null, user: null, isAuthenticated: false }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

export default useAuthStore;

Сега при рестартиране на приложението, потребителят остава автентикиран — токенът и данните му се възстановяват автоматично от AsyncStorage. Без допълнителен код.

Проследяване на хидратацията

Тук има една важна подробност. Когато използвате persist middleware с AsyncStorage, зареждането е асинхронно. Това означава, че при първия рендер store-ът може все още да не е хидратиран. Ето как да се справите с това:

// В компонент или навигация
import useAuthStore from '../stores/useAuthStore';

function AppNavigator() {
  const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
  const hasHydrated = useAuthStore.persist.hasHydrated();

  if (!hasHydrated) {
    return <SplashScreen />;
  }

  return isAuthenticated ? <MainStack /> : <AuthStack />;
}

TanStack Query: Автоматично управление на сървърни данни

Какво е TanStack Query

TanStack Query (бившият React Query) е библиотека за извличане, кеширане и синхронизиране на сървърно състояние. Тя автоматизира огромна част от работата, която преди се правеше ръчно — кеширане, дедупликация на заявки, фонови обновявания, повторни опити при грешки и управление на loading/error състояния.

Актуалната версия е TanStack Query v5 и тя на практика е стандарт за работа със сървърни данни. В модерни приложения тя управлява около 80% от всички данни.

Инсталация

npm install @tanstack/react-query

Начална конфигурация за React Native

TanStack Query изисква малко специална конфигурация за React Native — трябва да го информирате за фокуса на приложението и мрежовата свързаност:

// App.tsx
import React from 'react';
import { AppStateStatus, Platform } from 'react-native';
import { QueryClient, QueryClientProvider, focusManager } from '@tanstack/react-query';
import { useAppState } from './hooks/useAppState';
import { useOnlineManager } from './hooks/useOnlineManager';

function onAppStateChange(status: AppStateStatus) {
  if (Platform.OS !== 'web') {
    focusManager.setFocused(status === 'active');
  }
}

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,
      staleTime: 1000 * 60 * 5, // 5 минути
    },
  },
});

export default function App() {
  useOnlineManager();
  useAppState(onAppStateChange);

  return (
    <QueryClientProvider client={queryClient}>
      <AppNavigator />
    </QueryClientProvider>
  );
}

Хук за мрежова свързаност

Необходимо е да информирате TanStack Query за промени в мрежовата свързаност, така че да знае кога може да прави заявки:

// hooks/useOnlineManager.ts
import { useEffect } from 'react';
import { onlineManager } from '@tanstack/react-query';
import * as Network from 'expo-network';

export function useOnlineManager() {
  useEffect(() => {
    const subscription = Network.addNetworkStateListener((state) => {
      onlineManager.setOnline(!!state.isConnected);
    });
    return () => subscription.remove();
  }, []);
}

Хук за състоянието на приложението

// hooks/useAppState.ts
import { useEffect } from 'react';
import { AppState, AppStateStatus } from 'react-native';

export function useAppState(onChange: (status: AppStateStatus) => void) {
  useEffect(() => {
    const subscription = AppState.addEventListener('change', onChange);
    return () => subscription.remove();
  }, [onChange]);
}

useQuery: Извличане на данни

Основният хук за четене на данни е useQuery. Нека създадем хук за извличане на списък с продукти:

// hooks/queries/useProducts.ts
import { useQuery } from '@tanstack/react-query';
import { api } from '../utils/api';

interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
  imageUrl: string;
}

export function useProducts(category?: string) {
  return useQuery<Product[]>({
    queryKey: ['products', { category }],
    queryFn: async () => {
      const params = category ? `?category=${category}` : '';
      const response = await api.get(`/products${params}`);
      return response.data;
    },
  });
}

export function useProduct(productId: string) {
  return useQuery<Product>({
    queryKey: ['products', productId],
    queryFn: async () => {
      const response = await api.get(`/products/${productId}`);
      return response.data;
    },
    enabled: !!productId,
  });
}

Обърнете внимание на queryKey — това е уникалният идентификатор за заявката. Когато филтрирате по категория, ключът се променя и TanStack Query автоматично извлича нови данни. Кешът се управлява отделно за всяка уникална комбинация от ключове.

Използване в екран

// screens/ProductListScreen.tsx
import React from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import { useProducts } from '../hooks/queries/useProducts';

export default function ProductListScreen() {
  const { data: products, isLoading, error, refetch } = useProducts();

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" />
        <Text>Зареждане на продукти...</Text>
      </View>
    );
  }

  if (error) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Грешка при зареждане: {error.message}</Text>
        <TouchableOpacity onPress={() => refetch()}>
          <Text>Опитай отново</Text>
        </TouchableOpacity>
      </View>
    );
  }

  return (
    <FlatList
      data={products}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ padding: 16, borderBottomWidth: 1, borderColor: '#eee' }}>
          <Text style={{ fontSize: 18 }}>{item.name}</Text>
          <Text style={{ color: '#666' }}>{item.price.toFixed(2)} лв.</Text>
        </View>
      )}
      onRefresh={refetch}
      refreshing={isLoading}
    />
  );
}

useMutation: Промяна на данни

За операции, които променят данни (създаване, обновяване, изтриване), използваме useMutation:

// hooks/mutations/useCreateOrder.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../utils/api';

interface OrderData {
  products: { id: string; quantity: number }[];
  shippingAddress: string;
  paymentMethod: string;
}

export function useCreateOrder() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (orderData: OrderData) => {
      const response = await api.post('/orders', orderData);
      return response.data;
    },
    onSuccess: () => {
      // Инвалидиране на свързани заявки
      queryClient.invalidateQueries({ queryKey: ['orders'] });
      queryClient.invalidateQueries({ queryKey: ['products'] });
    },
    onError: (error) => {
      console.error('Грешка при създаване на поръчка:', error);
    },
  });
}

Обединяване на Zustand и TanStack Query: Реална архитектура

Структура на проекта

Ето препоръчителната файлова структура, която лично аз използвам в проектите си:

src/
├── stores/                    # Zustand stores (клиентско състояние)
│   ├── useAuthStore.ts
│   ├── useSettingsStore.ts
│   └── useCartStore.ts
├── hooks/
│   ├── queries/               # TanStack Query хукове за четене
│   │   ├── useProducts.ts
│   │   ├── useOrders.ts
│   │   └── useUserProfile.ts
│   ├── mutations/             # TanStack Query хукове за промяна
│   │   ├── useCreateOrder.ts
│   │   ├── useUpdateProfile.ts
│   │   └── useDeleteProduct.ts
│   ├── useAppState.ts
│   └── useOnlineManager.ts
├── screens/
├── components/
└── utils/
    ├── api.ts                 # API клиент (axios/fetch)
    └── queryClient.ts         # Конфигурация на QueryClient

Тази структура е чиста и интуитивна — веднага виждате къде какво живее.

Практически пример: Кошница с Zustand + продукти с TanStack Query

Сега идва интересната част. Нека разгледаме пълен пример, в който двете библиотеки работят заедно:

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

interface CartItem {
  productId: string;
  name: string;
  price: number;
  quantity: number;
}

interface CartState {
  items: CartItem[];
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (productId: string) => void;
  updateQuantity: (productId: string, quantity: number) => void;
  clearCart: () => void;
  getTotalPrice: () => number;
  getTotalItems: () => number;
}

const useCartStore = create<CartState>()(
  persist(
    (set, get) => ({
      items: [],
      addItem: (item) =>
        set((state) => {
          const existingItem = state.items.find(
            (i) => i.productId === item.productId
          );
          if (existingItem) {
            return {
              items: state.items.map((i) =>
                i.productId === item.productId
                  ? { ...i, quantity: i.quantity + 1 }
                  : i
              ),
            };
          }
          return { items: [...state.items, { ...item, quantity: 1 }] };
        }),
      removeItem: (productId) =>
        set((state) => ({
          items: state.items.filter((i) => i.productId !== productId),
        })),
      updateQuantity: (productId, quantity) =>
        set((state) => ({
          items: state.items.map((i) =>
            i.productId === productId ? { ...i, quantity } : i
          ),
        })),
      clearCart: () => set({ items: [] }),
      getTotalPrice: () =>
        get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
      getTotalItems: () =>
        get().items.reduce((sum, item) => sum + item.quantity, 0),
    }),
    {
      name: 'cart-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

export default useCartStore;

Свързване на Zustand филтри с TanStack Query

Една от най-мощните техники (и лично моята любима) е да използвате стойности от Zustand store като част от queryKey на TanStack Query. Така при промяна на филтрите, данните се преизвличат автоматично:

// stores/useFilterStore.ts
import { create } from 'zustand';

interface FilterState {
  category: string | null;
  priceRange: [number, number];
  searchQuery: string;
  sortBy: 'price' | 'name' | 'rating';
  setCategory: (category: string | null) => void;
  setPriceRange: (range: [number, number]) => void;
  setSearchQuery: (query: string) => void;
  setSortBy: (sort: 'price' | 'name' | 'rating') => void;
  resetFilters: () => void;
}

const useFilterStore = create<FilterState>((set) => ({
  category: null,
  priceRange: [0, 10000],
  searchQuery: '',
  sortBy: 'name',
  setCategory: (category) => set({ category }),
  setPriceRange: (priceRange) => set({ priceRange }),
  setSearchQuery: (searchQuery) => set({ searchQuery }),
  setSortBy: (sortBy) => set({ sortBy }),
  resetFilters: () =>
    set({
      category: null,
      priceRange: [0, 10000],
      searchQuery: '',
      sortBy: 'name',
    }),
}));

export default useFilterStore;
// hooks/queries/useFilteredProducts.ts
import { useQuery } from '@tanstack/react-query';
import useFilterStore from '../../stores/useFilterStore';
import { api } from '../../utils/api';

export function useFilteredProducts() {
  const filters = useFilterStore((state) => ({
    category: state.category,
    priceRange: state.priceRange,
    searchQuery: state.searchQuery,
    sortBy: state.sortBy,
  }));

  return useQuery({
    queryKey: ['products', 'filtered', filters],
    queryFn: async () => {
      const response = await api.get('/products', { params: filters });
      return response.data;
    },
    placeholderData: (previousData) => previousData,
  });
}

Тук placeholderData (заместващо keepPreviousData от v4) показва предишните данни, докато новите се зареждат. Резултатът? Плавно потребителско изживяване без мигане на екрана.

Оптимистични обновявания с useMutation

За по-бърз отклик можете да обновите UI-а преди сървърът да потвърди операцията. Това е особено важно за действия като добавяне в любими — потребителят очаква моментална реакция:

// hooks/mutations/useToggleFavorite.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../../utils/api';

export function useToggleFavorite() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (productId: string) => {
      return api.post(`/products/${productId}/favorite`);
    },
    onMutate: async (productId) => {
      // Отмяна на текущи заявки
      await queryClient.cancelQueries({ queryKey: ['products'] });

      // Запазване на предишното състояние
      const previousProducts = queryClient.getQueryData(['products']);

      // Оптимистично обновяване
      queryClient.setQueryData(['products'], (old: any[]) =>
        old?.map((product) =>
          product.id === productId
            ? { ...product, isFavorite: !product.isFavorite }
            : product
        )
      );

      return { previousProducts };
    },
    onError: (_err, _productId, context) => {
      // Възстановяване при грешка
      queryClient.setQueryData(['products'], context?.previousProducts);
    },
    onSettled: () => {
      // Инвалидиране за сигурност
      queryClient.invalidateQueries({ queryKey: ['products'] });
    },
  });
}

Обновяване при фокус на екрана

При навигация между екрани е добра практика да обновявате данните, когато екранът отново получи фокус. TanStack Query v5 предлага елегантен начин за това чрез свойството subscribed:

// screens/OrdersScreen.tsx
import React from 'react';
import { View, Text, FlatList } from 'react-native';
import { useIsFocused } from '@react-navigation/native';
import { useQuery } from '@tanstack/react-query';
import { api } from '../utils/api';

export default function OrdersScreen() {
  const isFocused = useIsFocused();

  const { data: orders, isLoading } = useQuery({
    queryKey: ['orders'],
    queryFn: () => api.get('/orders').then((res) => res.data),
    subscribed: isFocused,
  });

  if (isLoading) return <Text>Зареждане на поръчки...</Text>;

  return (
    <FlatList
      data={orders}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ padding: 16 }}>
          <Text>Поръчка #{item.id}</Text>
          <Text>Статус: {item.status}</Text>
          <Text>{item.total.toFixed(2)} лв.</Text>
        </View>
      )}
    />
  );
}

Когато subscribed е false, заявката спира да получава обновявания и няма да предизвиква ре-рендери. Доста полезно за приложения с много екрани — спестява излишни мрежови заявки.

Кога какво да използвате: Бърза справка

Ето кратка справочна таблица, която можете да запазите:

  • Локално състояние на компонент (формуляри, тогъл бутони) → useState / useReducer
  • Тема, автентикация, потребителски предпочитанияZustand
  • Данни от API / база данниTanStack Query
  • Рядко променящи се стойности, достъпни навсякъде (locale) → React Context
  • Големи корпоративни приложения с екипиRedux Toolkit (все пак си има своето място)

Типични грешки, които да избягвате

1. Съхраняване на сървърни данни в Zustand

Това е най-честата грешка и аз самият съм я правил в ранните си проекти. Извличате данни от API и ги записвате в Zustand store. Не правете това — оставете TanStack Query да управлява кеширането.

// ❌ Грешно — съхраняване на API данни в Zustand
const useStore = create((set) => ({
  products: [],
  fetchProducts: async () => {
    const res = await api.get('/products');
    set({ products: res.data }); // Не правете това!
  },
}));

// ✅ Правилно — използвайте TanStack Query
function useProducts() {
  return useQuery({
    queryKey: ['products'],
    queryFn: () => api.get('/products').then((r) => r.data),
  });
}

2. Прекалено голям Zustand store

Не слагайте всичко в един store. Разделете логически свързаните данни в отделни store-ове — auth, settings, cart, ui. Всеки store трябва да има една ясна отговорност. Ако store-ът ви прехвърли 100 реда, вероятно е време да го разделите.

3. Липса на инвалидиране на кеша

Когато правите мутация, не забравяйте да инвалидирате свързаните заявки. Без invalidateQueries, кешираните данни остават остарели и потребителят няма да вижда промените. Изглежда очевидно, но се случва по-често отколкото си мислите.

4. Пренебрегване на staleTime

По подразбиране staleTime е 0 — данните се считат за остарели веднага. За данни, които не се променят често (категории, настройки), задайте по-дълъг staleTime. Ще намалите излишните заявки и ще подобрите усещането за бързина.

Производителност и оптимизация

Селективно абониране в Zustand

Винаги селектирайте само полетата, от които компонентът се нуждае. Избягвайте извличането на целия store:

// ❌ Предизвиква ре-рендер при всяка промяна в store-а
const state = useCartStore();

// ✅ Ре-рендер само при промяна на items
const items = useCartStore((state) => state.items);

// ✅ Ре-рендер само при промяна на общия брой
const totalItems = useCartStore((state) => state.getTotalItems());

Дедупликация на заявки

TanStack Query автоматично дедуплицира заявки с еднакъв queryKey. Ако три компонента на екрана извикват useProducts(), ще се направи само една HTTP заявка. Това е една от най-мощните оптимизации — и я получавате безплатно, без допълнителен код.

MMKV за по-бързо persist

За по-добра производителност при persist, помислете за замяна на AsyncStorage с react-native-mmkv. MMKV е значително по-бърз (говорим за порядъци разлика) и поддържа криптиране:

npm install react-native-mmkv
import { MMKV } from 'react-native-mmkv';
import { StateStorage } from 'zustand/middleware';

const storage = new MMKV();

const mmkvStorage: StateStorage = {
  getItem: (name) => storage.getString(name) ?? null,
  setItem: (name, value) => storage.set(name, value),
  removeItem: (name) => storage.delete(name),
};

// Използване в persist middleware
persist(storeDefinition, {
  name: 'my-store',
  storage: createJSONStorage(() => mmkvStorage),
});

DevTools за дебъгване

За TanStack Query в Expo/React Native проекти можете да инсталирате специализиран DevTools плъгин:

npm install tanstack-query-dev-tools-expo-plugin --save-dev
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useSyncQueries } from 'tanstack-query-dev-tools-expo-plugin';

const queryClient = new QueryClient();

export function App() {
  useSyncQueries({ queryClient });

  return (
    <QueryClientProvider client={queryClient}>
      {/* Компоненти */}
    </QueryClientProvider>
  );
}

За Zustand можете да използвате devtools middleware, което интегрира store-а с Redux DevTools (да, точно така — Redux DevTools работи и с Zustand):

import { devtools } from 'zustand/middleware';

const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set(
        (state) => ({ count: state.count + 1 }),
        false,
        'increment'
      ),
    }),
    { name: 'MyStore' }
  )
);

Заключение

Комбинацията от Zustand и TanStack Query предлага елегантно и лесно за поддръжка решение за управление на състоянието в React Native. Разделяйки клиентското и сървърното състояние, получавате:

  • По-малко код — до 40% намаление на бойлерплейт в сравнение с Redux
  • По-добра производителност — автоматична оптимизация на ре-рендерите и мрежовите заявки
  • По-лесна поддръжка — ясно разделение на отговорностите
  • По-добро потребителско изживяване — автоматично кеширане, фоново обновяване и оптимистични обновявания

Тази архитектура не е просто мода — тя е резултат от години опит на общността и се е доказала в стотици продукционни приложения. Ако стартирате нов React Native проект или обмисляте миграция от Redux, дайте шанс на тази комбинация. Няма да съжалявате.

За Автора Marcus Adeyemi

Marcus is a senior React Native engineer based in Berlin with twelve years in mobile, the last seven of them in JavaScript-driven cross-platform work. He spent three years at SoundCloud rewriting the listener app's playback queue and offline-download stack on top of TrackPlayer, and another two and a half years at Delivery Hero leading the rider app's reliability work across 30+ markets. He started out as a native iOS developer at a smaller agency shipping white-label banking apps, which still shows up in his writing whenever the topic turns to bridging Swift code or wrangling Xcode build settings. Most of his posts here cover Reanimated 3 and Gesture Handler internals, CodePush versus EAS Update tradeoffs, and the kind of Hermes crash reports that only show up in production. He occasionally speaks at React Native EU.