Volver al Blog
AppsDesarrolloSoftware

Gestión de Estado y Caché de Red en React Native con Zustand y TanStack Query

Brayan Developer
5 min de lectura
Gestión de Estado y Caché de Red en React Native con Zustand y TanStack Query
Optimiza la arquitectura de datos de tus aplicaciones móviles con React Native y Expo combinando Zustand para estado de cliente y TanStack Query para caché de servidor.

En el desarrollo de aplicaciones móviles con React Native y Expo, uno de los errores arquitectónicos más comunes es mezclar el estado del cliente (temas, modales abiertos, filtros de búsqueda, credenciales de sesión) con el estado del servidor (catálogo de productos, notificaciones, historial de órdenes). Esta sobrecarga en gestores globales tradicionales como Redux produce código redundante (boilerplate), problemas de re-renderizados innecesarios y sincronizaciones manuales propensas a errores.

Portada

La Regla de Oro: Separación de Responsabilidades#

Para construir aplicaciones móviles ultra fluidas a 60/120 FPS debemos diferenciar claramente dos dominios:

+-----------------------------------------------------------------------------------+
|                           ARQUITECTURA DE DATOS EN LA APP                         |
+-----------------------------------------------------------------------------------+
|                                                                                   |
|  +-------------------------------------+   +-----------------------------------+  |
|  |     ESTADO DE CLIENTE (UI STATE)    |   |     ESTADO DE SERVIDOR (ASYNC)    |  |
|  |           Herramienta: ZUSTAND      |   |    Herramienta: TANSTACK QUERY    |  |
|  +-------------------------------------+   +-----------------------------------+  |
|  | • Modo oscuro / Tema visual         |   | • Caché persistente de peticiones |  |
|  | • Tokens de sesión & perfil activo  |   | • Revalidación en segundo plano   |  |
|  | • Estado de modales y drawers       |   | • Mutaciones optimistas           |  |
|  | • Filtros locales de listados       |   | • Paginación infinita             |  |
|  +-------------------------------------+   +-----------------------------------+  |
|                                                                                   |
+-----------------------------------------------------------------------------------+

1. Estado de Cliente con Zustand: Ligero y Reactivo#

Zustand es una librería minimalista basada en hooks que no requiere envolver la aplicación en providers engorrosos y permite suscripciones atómicas a propiedades específicas.

Store de Autenticación y UI (src/stores/useAuthStore.ts)#

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

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'client';
}

interface AuthState {
  token: string | null;
  user: User | null;
  isAuthenticated: boolean;
  theme: 'light' | 'dark' | 'system';
  setAuth: (token: string, user: User) => void;
  logout: () => void;
  setTheme: (theme: 'light' | 'dark' | 'system') => void;
}

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

Suscripción Atómica sin Re-renders Innecesarios#

import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { useAuthStore } from '@/stores/useAuthStore';

export function UserHeader() {
  // Solo se suscribe al nombre del usuario. No re-renderizará si cambia el tema.
  const userName = useAuthStore((state) => state.user?.name);
  const logout = useAuthStore((state) => state.logout);

  return (
    <View className="flex-row justify-between items-center p-4 bg-slate-900">
      <Text className="text-white font-bold text-lg">Hola, {userName || 'Invitado'}</Text>
      <TouchableOpacity onPress={logout} className="bg-rose-600 px-3 py-1.5 rounded-lg">
        <Text className="text-white text-xs font-semibold">Cerrar Sesión</Text>
      </TouchableOpacity>
    </View>
  );
}

2. Estado de Servidor con TanStack Query (React Query v5)#

TanStack Query gestiona automáticamente el ciclo de vida de las peticiones de red: reintentos tras caídas de conexión, deduplicación de llamadas simultáneas y stale-while-revalidate.

Configuración del QueryClient con Persistencia Offline#

// src/lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutos de validez en memoria
      gcTime: 1000 * 60 * 60 * 24, // 24 horas en caché recolector
      retry: 2,
      refetchOnReconnect: true,
    },
  },
});

Hook Personalizado para Consultar Productos#

// src/hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
}

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

Mutación Optimista para Agregar al Carrito#

Las actualizaciones optimistas reflejan el cambio en la UI instantáneamente antes de que el servidor responda, revirtiéndolo solo si ocurre un fallo de red:

// src/hooks/useAddToCart.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';

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

  return useMutation({
    mutationFn: async (productId: string) => {
      return axios.post('/api/cart/items', { productId, quantity: 1 });
    },
    onMutate: async (productId) => {
      // 1. Cancelar refetches salientes
      await queryClient.cancelQueries({ queryKey: ['cart'] });

      // 2. Tomar captura del estado previo
      const previousCart = queryClient.getQueryData(['cart']);

      // 3. Actualizar la caché de forma optimista
      queryClient.setQueryData(['cart'], (old: any) => ({
        ...old,
        itemsCount: (old?.itemsCount || 0) + 1,
      }));

      // Retornar contexto de rollback
      return { previousCart };
    },
    onError: (err, productId, context) => {
      // Revertir a la captura previa en caso de error
      if (context?.previousCart) {
        queryClient.setQueryData(['cart'], context.previousCart);
      }
    },
    onSettled: () => {
      // Revalidar para asegurar sincronización con la base de datos
      queryClient.invalidateQueries({ queryKey: ['cart'] });
    },
  });
}

Comparativa de Rendimiento#

Criterio Redux + Thunks Tradicional Zustand + TanStack Query
Tamaño de Bundle (JS) ~30 KB + dependencias < 6 KB combinado
Líneas de Código Boilerplate Alto (actions, reducers, types) Mínimo (definición directa)
Manejo de Estados de Carga/Error Manual (isLoading, error en store) Automático (isPending, isError)
Rendimiento de Renderizado Suscripciones complejas con reselect Selectores directos atómicos

Conclusión#

Separar el estado de UI con Zustand y la sincronización de datos de servidor con TanStack Query es el estándar moderno en la industria para aplicaciones móviles con React Native y Expo de alto rendimiento.

¿Buscas desarrollar una aplicación móvil para iOS y Android con arquitectura limpia, escalable y optimizada? Explora nuestros Servicios de Desarrollo de Apps Móviles o ponte en contacto con nosotros hoy mismo.

Etiquetas
React NativeZustandTanStack QueryExpoApps MóvilesTypeScript
Compartir:XLinkedInWhatsApp

¿Te gustaría profundizar en estos temas?

Aprende sobre desarrollo de software, apps a medida, automatizaciones con N8N, Next.js y Cloud con casos reales.

Hablemos