Casi todas las apps de React Native guardan dos tipos de estado que no se parecen en nada: el estado de servidor y el estado de cliente. Distínguelos y la elección de librería casi se responde sola; mézclalos y acabas construyendo a mano lo que una librería te daría gratis.
Estos dos posts son un pequeño descanso de la serie de Module Federation, porque el trabajo federado que viene se apoya justo en esta distinción. Es más fácil dejarla clara primero en una app sencilla.
Dos combinaciones aguantan bien a medida que crece una app. Con Redux Toolkit y RTK Query mantienes un solo store, pero el estado de servidor y el de cliente los llevan dos herramientas distintas dentro de él: RTK Query es una capa de estado de servidor hecha a propósito, con toda su maquinaria de caché incluida, y el estado de cliente se queda en slices normales de createSlice. Con TanStack Query y Zustand repartes en dos librerías en su lugar, con la caché del servidor viviendo en el propio cliente de TanStack, fuera de cualquier store, y el estado de cliente en un pequeño store de Zustand. En ambos casos los dos tipos reciben un trato distinto y nunca se fuerzan a pasar por uno solo. El atajo que rompe eso es un único store de propósito general, un thunk por petición y lógica de caché cultivada a mano. Va bien en una app pequeña y se convierte en un segundo trabajo a medida que crece.
Dos tipos de estado
El estado de servidor es una copia local de algo que un servidor posee: un perfil de /users/me, una lista de productos, el estado de un pedido. La copia es siempre solo una instantánea, y el original puede cambiar justo después de que lo leas. De ese hecho sale todo el trabajo. Una copia de datos remotos necesita caché, para no volver a pedir los datos en cada montaje; deduplicación, para que dos componentes que piden el mismo recurso lancen una sola petición; refetch en segundo plano, para que la pantalla se refresque sin ruido; reintentos, para la red que se cae; invalidación, para que una escritura pueda marcar como obsoletos los datos relacionados; y recolección de basura, para descartar lo que nadie está observando. Nada de eso es propio de React. Es lo que cualquier cliente le debe a unos datos que no son suyos.
El estado de cliente es la otra mitad: datos que la app posee por completo. La cuenta seleccionada, un orden de clasificación, el borrador de un formulario, si un modal está abierto, los tokens de autenticación que guardas tras el login (dónde los guardas de forma segura es un tema aparte), el tema y el idioma del usuario. Hay que guardarlo, tiene que ser reactivo, y de vez en cuando tiene que sobrevivir a un reinicio. Esa es casi toda la lista. No puede quedar obsoleto, así que no necesita caché; no hay nada que volver a pedir, así que no necesita sincronización en segundo plano. Los dos no son grados de una misma cosa. Tienen trabajos distintos, y una librería hecha para uno es la herramienta equivocada para el otro.
Qué pide cada mitad
El estado de servidor quiere una librería que ya tenga la maquinaria. Constrúyela a mano y cada feature repite el mismo andamiaje: una petición, un flag de carga, un campo de error y una rama para cada uno de los tres estados en los que puede estar un fetch. Una librería de estado de servidor te lo entrega todo detrás de un hook. En Redux, esa librería es RTK Query:
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: builder => ({
getProfile: builder.query<Profile, void>({
query: () => '/users/me',
}),
}),
});
export const { useGetProfileQuery } = api;
El componente lee una sola línea, y la caché, la deduplicación, los reintentos y el refetch al recuperar el foco vienen con ella:
const { data: profile, isLoading, error } = useGetProfileQuery();
Fuera de Redux, TanStack Query hace el mismo trabajo con la misma forma: declara la query y deja que la librería gestione la caché y el ciclo de vida.
export const useProfile = () =>
useQuery({ queryKey: ['profile'], queryFn: getProfile });
El estado de cliente pide casi nada en comparación. Una preferencia no tiene servidor con el que compararse, así que lo que la app guarde es correcto por definición, y una caché o un refetch resolverían un problema que no existe. Un pequeño store es suficiente. Con Zustand:
export const useSettings = create<Settings>()(set => ({
theme: 'system',
setTheme: theme => set({ theme }),
}));
Sin reducer, sin dispatch, sin ciclo de vida. En Redux lo mismo es un createSlice normal con un par de setters. En ambos casos guarda un valor y deja que los componentes reaccionen a él. El error en las dos direcciones es el mismo: mete estado de cliente en una herramienta de estado de servidor y cargas con una maquinaria que nunca usa; haz que un único store hecho a mano lleve las dos cosas y reconstruyes a mano el trabajo de la librería.
Qué reparto, y cuándo
No hay un ganador universal entre las dos combinaciones, y desconfiaría de cualquier post que te ofreciera uno. Si tu equipo ya vive en Redux, RTK Query es el camino más corto, porque deja la maquinaria de estado de servidor dentro del store que ya tienes en marcha. Si llegas a una app nueva sin compromiso con Redux, TanStack Query y Zustand son dos herramientas centradas que encajan sin pisarse. Y en una app pequeña, un solo store con un thunk por petición es un punto de partida perfectamente razonable. Vigila el día en que te encuentres escribiendo lógica de caché a mano: esa es la señal para recurrir a una herramienta que ya la trae.
Así que la próxima vez que vayas a coger un slice para guardar una respuesta de una API, párate antes en la pregunta más pequeña: ¿necesitas maquinaria de estado de servidor, o solo un sitio donde guardar un valor que unos cuantos componentes puedan observar?
La pregunta se afina aún más en cuanto las features dejan de compartir un mismo codebase. Cuando cada una se despliega como su propia app y se carga en runtime, saber qué caché manda deja de ser una comodidad y pasa a ser algo que tienes que coordinar. Ahí es donde la serie de Module Federation retoma el hilo, y resulta mucho más fácil de razonar una vez que la versión de app sencilla te sale de forma natural.
Antes de eso, el próximo post se queda en la mitad de servidor y mira una sola diferencia entre estas herramientas: cómo los cache tags de RTK Query y las query keys de TanStack resuelven la misma invalidación, y por qué esa pequeña diferencia de API decide cómo aguanta cada uno cuando hay más de un equipo haciendo commits sobre él.