Inicio / Artículos / React Query y Redux: Repensando el estado del servidor en aplicaciones grandes

React Query y Redux: Repensando el estado del servidor en aplicaciones grandes

Aprende por qué una aplicación de chat en producción utilizó TanStack Query en lugar de Redux para gestionar los datos del servidor, y dónde sigue teniendo cabida Redux en la arquitectura moderna de React.

2701 palabras

Imagínese a un desarrollador que pasa de proyectos más pequeños a una empresa basada en productos, ansioso por ver finalmente cómo se construye realmente un software a gran escala y de calidad profesional. Después de años trabajando en aplicaciones frontales, unirse a un equipo responsable del software utilizado por millones de personas cambia la forma en que se evalúa el código.

Deja de preguntarse simplemente: "¿Funciona esta función?"

En su lugar, comienza a preguntarse:

¿Cómo resiste esta arquitectura la carga de millones de usuarios? ¿Cómo se mantiene sincronizado el estado en toda la aplicación? ¿Qué sucede cuando diez componentes diferentes necesitan los mismos datos? ¿Cómo logran los ingenieros que una base de código tan grande siga siendo mantenible a medida que el producto crece?

Ahora imagine que a ese desarrollador se le asigna uno de los módulos centrales del producto: una aplicación de chat, similar en espíritu a Slack.

Esto no es una función secundaria escondida en un rincón de la aplicación.

La experiencia de chat está en el corazón del producto, sirviendo a millones de usuarios y estando estrechamente conectada con flujos de trabajo empresariales críticos, experiencias que generan ingresos y algunos de los clientes corporativos más importantes de la compañía.

Por lo tanto, es lógico que, al examinar el código por primera vez, este desarrollador tuviera expectativas bastante predecibles.

Una aplicación de chat a esta escala inevitablemente maneja una enorme cantidad de información que muchas pantallas necesitan compartir: hilos y sus mensajes, cuántos mensajes quedan sin leer, quiénes participan en una conversación, navegación por el historial, ediciones y otras operaciones de escritura, indicadores de carga en curso y actualizaciones en tiempo real.

Dado todo esto, uno esperaría encontrar los elementos habituales en un gran códigobase de React:

Redux, Zustand o al menos alguna forma de gestión de estado global.

Pero después de revisar el código, no se encuentra ningún almacén de estado.

Ninguna configuración de Redux.

Ningún Zustand.

Ningún objeto Context extenso que contenga los datos compartidos de la aplicación.

A primera vista, sería fácil suponer que simplemente se pasó por alto algo al explorar el repositorio.

Pero al investigar más a fondo, se descubre qué es lo que realmente impulsa la capa de datos compartidos.

TanStack Query.

Es un descubrimiento realmente sorprendente si tu modelo mental de esta biblioteca es limitado.

Muchos desarrolladores consideran a React Query principalmente como una herramienta para obtener datos de API, almacenar en caché las respuestas, seguir el estado de carga y volver a obtener datos cuando algo cambia.

Pero en esta aplicación de nivel profesional que atiende a millones de usuarios, el caché de consultas hacía mucho más que eso. Funcionaba como el hogar compartido para todos los datos propiedad del servidor en toda la aplicación.

Esa observación cuestiona una suposición arraigada sobre la arquitectura del frontend.

Tal vez la pregunta correcta no sea:

"¿Por qué no hay un almacén Redux aquí?"

Tal vez debería ser:

"¿Por qué los datos propiedad del servidor necesitarían residir en Redux desde un principio?"

Esa pregunta abre un debate mucho más profundo sobre cómo modelamos el estado en las aplicaciones React, y por qué, en muchos sistemas del mundo real, TanStack Query puede eliminar silenciosamente una parte sorprendente de la maquinaria de estado global que los equipos han construido históricamente a mano.

De Redux a React Query

Hubo un tiempo en el que conectar una llamada a API a una aplicación React parecía una tarea sencilla.

Luego apareció Redux.

La llamada a API en sí se mantuvo simple; todo lo que se añadió alrededor de ella se complicó.

Se definía una acción.

Se escribía un reducer.

Se añadían indicadores de carga y errores.

Se enviaba la acción.

La respuesta se guardaba en el almacén de estado.

Se escribía un selector para leerla nuevamente.

Se conectaba el componente a todo eso.

Luego, semanas o meses después, alguien inevitablemente preguntaba:

"¿Por qué estos datos parecen obsoletos?"

Y la solución suele ser otra acción para forzar una nueva carga.

Después de pasar por ese ciclo repetidamente, se hace evidente un patrón incómodo:

Los herramientas de estado global se utilizaban con frecuencia para gestionar algo que en realidad nunca fue un asunto relacionado con el estado del cliente desde un principio.

El backend era el verdadero propietario de los datos.

El frontend solo los consumía.

Esa distinción es una parte importante de por qué TanStack Query se ha convertido en una alternativa tan interesante en las aplicaciones modernas de React.

Primero, ¿qué es exactamente React Query?

Antes de explorar cómo reduce la necesidad de Redux, vale la pena aclarar un malentendido común.

TanStack Query, anteriormente llamado React Query, no es un sustituto de useState, Redux o Zustand.

Su responsabilidad principal es gestionar el estado del servidor: datos que provienen fuera de tu aplicación React y que necesitan ser recuperados, almacenados en caché, sincronizados, actualizados y, eventualmente, considerados obsoletos.

React Query no se trata solo de enviar una solicitud y guardar la respuesta en el estado local de un componente.

La propia documentación de TanStack describe la biblioteca específicamente en torno a la obtención, almacenamiento en caché, sincronización y actualización del estado del servidor.

Imagínese los datos de la siguiente manera:

Users
Projects
Messages
Notifications
Orders
Analytics

Su frontend en realidad no posee ninguna de esta información.

El backend sí la posee.

React Query actúa como la capa intermedia entre su interfaz de usuario y ese backend, asumiendo la responsabilidad del ciclo de vida de esos datos.

En el núcleo de este diseño se encuentra el Query Cache.

Según la documentación actual de TanStack, QueryCache es la capa de almacenamiento para las consultas: alberga sus datos, metadatos y estado. Un QueryClient gestiona este caché y expone las API que una aplicación utiliza para leerlo, actualizarlo, invalidar entradas e interactuar con él de otras maneras.

Esa es precisamente la razón por la cual varias partes no relacionadas de una aplicación pueden solicitar la misma consulta y obtener resultados consistentes:

const { data } = useQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects
})

No obstante, React Query hace mucho más que almacenar en caché una única respuesta.

Gestiona el almacenamiento en caché, la deduplicación de solicitudes, el seguimiento de la actualidad, la recarga en segundo plano, la lógica de intentos repetidos, la recolección de basura, las mutaciones y la invalidación, todo como parte del mismo sistema.

Por ejemplo, después de que se actualice un proyecto:

const queryClient = useQueryClient()
await updateProject(project)
queryClient.invalidateQueries({
  queryKey: ['projects']
})

En lugar de indicar manualmente a diez componentes separados cómo actualizar su copia local del proyecto, simplemente se le dice al sistema de consultas:

">Los datos del servidor detrás de esta consulta pueden ya no ser precisos."

A partir de ahí, el caché se encarga de volver a validarlos.

Esa es la idea fundamental en la que se basa TanStack Query.

No intenta convertirse en un segundo Redux.

Eso le da al estado del servidor un ciclo de vida propio.

Una vez que comienzas a tratar el estado del servidor como algo fundamentalmente diferente al estado del cliente, resulta mucho más fácil entender por qué una aplicación que, a primera vista, parece necesitar un almacén Redux extenso podría no necesitarlo en absoluto.

Redux nunca fue el problema

Para ser justos, Redux en sí mismo no merece ninguna culpa aquí.

Redux Toolkit sigue siendo el enfoque oficialmente recomendado para desarrollar con Redux, y Redux sigue siendo útil siempre que una aplicación realmente necesite un estado complejo en el lado del cliente, transiciones predecibles entre estados, pipelines de middleware o un modelo de estado unificado.

Los problemas surgen cuando los desarrolladores depositan absolutamente todo en un único almacén global.

Imagina una aplicación con esta estructura:

{
  user: {},
  projects: [],
  teams: [],
  notifications: [],
  orders: [],
  products: [],
  analytics: {},
  theme: "dark",
  sidebarOpen: true
}

A primera vista, todo eso parece pertenecer a la aplicación.

Pero en realidad no es así.

Pruebe a hacer una pregunta directa:

¿Quién es el dueño real de estos datos?

¿Es la lista de proyectos algo que controla la aplicación React?

No del todo.

El backend es el verdadero dueño.

¿Podría otro usuario modificar un pedido mientras la pestaña del navegador permanece abierta?

Por supuesto que sí.

¿Podría aparecer una notificación sin que se ejecute ninguna acción en el código React?

Sí, absolutamente.

¿Podría el servidor revocar o cambiar unilateralmente los permisos de un usuario?

Sin duda alguna.

Por lo tanto, una parte importante de ese “estado de la aplicación” en realidad no pertenece al frontend.

Se trata del estado del servidor.

Y el estado del servidor conlleva una categoría completamente diferente de desafíos.

Tienes que obtenerlo.

Tienes que almacenarlo en caché.

Tienes que determinar cuándo se vuelve obsoleto.

Tienes que obtenerlo nuevamente.

Tienes que mantenerlo sincronizado después de que ocurran mutaciones.

Tienes que gestionar los indicadores de carga y los estados de error.

Tienes que tener en cuenta las reintentos y las conexiones de red interrumpidas.

Este es exactamente el conjunto de problemas para los cuales se creó TanStack Query.

El cambio arquitectónico

Una configuración convencional centrada en Redux suele seguir este flujo:

API
 ↓
Async action / thunk
 ↓
Reducer
 ↓
Redux Store
 ↓
Selector
 ↓
React Component

En muchas aplicaciones basadas en API, ese flujo puede reorganizarse de la siguiente manera:

API
 ↓
TanStack Query
 ↓
Query Cache
 ↓
React Component

A simple vista, la diferencia podría parecer menor.

Pero no lo es.

El cambio real es que ya no se construye manualmente toda la infraestructura necesaria para el estado del servidor.

Tomemos algo tan común como una lista de proyectos.

Bajo Redux, normalmente se comenzaría con:

const initialState = {
  data: [],
  loading: false,
  error: null
}

Luego se agrega una acción asíncrona:

dispatch(fetchProjects())

Seguida de la lógica del reducer que abarca:

pending
fulfilled
rejected

Y finalmente, un selector:

const projects = useSelector(
  state => state.projects.data
)

Ahora compárelo con la versión de TanStack Query:

const { data, isPending, error } = useQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects
})

Esto no es simplemente una reducción en las líneas de código.

La consulta en sí misma se convierte en la abstracción que envuelve el recurso del servidor.

La clave de la consulta da nombre al recurso que se está monitoreando.

El caché conserva el resultado.

La consulta controla su propio estado.

Y cualquier número de componentes puede acceder al mismo valor almacenado en caché.

QueryCache de TanStack Query existe específicamente para almacenar los resultados de las consultas junto con su estado asociado, y QueryClient le proporciona la interfaz para trabajar con ese caché.

El caché de consultas es básicamente un almacén global para datos del servidor

Este podría ser el concepto más importante aquí.

Muchos desarrolladores escuchan la frase:

"React Query tiene un caché."

y asumen que significa:

"Entonces solo almacena respuestas de API en caché."

Pero va más allá de eso.

El caché de consultas se convierte efectivamente en la fuente única y compartida de información sobre el estado del servidor de su aplicación.

Imagínese tres componentes separados:

Dashboard
   |
   +── ProjectList
   |
   +── ProjectSidebar
   |
   +── RecentProjects

Cada uno de ellos necesita los mismos datos, identificados por:

['projects']

No hay necesidad de canalizar manualmente esos datos del servidor a Redux primero y luego hacer que los tres componentes lean desde el almacén.

Solo hay que solicitar la misma consulta donde sea necesaria:

useQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects
})

TanStack Query se encarga de compartir y almacenar en caché esos datos en segundo plano.

La arquitectura resultante puede quedar así:

               React App
                  |
        ┌─────────┴─────────┐
        |                   |
   Client State        Server State
        |                   |
 Redux / Zustand       TanStack Query
        |                   |
     UI state         Query Cache

De repente, todo el sistema se vuelve mucho más sencillo de comprender.

¿Puede React Query reemplazar a Redux?

En algunas aplicaciones, sí, realmente puede hacerlo.

Pero aquí está la nuanza crucial:

No reemplaza a Redux al ser una versión superior de él.

En lugar de eso, elimina la necesidad de depender de Redux para el estado del servidor desde un principio.

Eso es una afirmación significativamente diferente.

La propia documentación de TanStack Query describe explícitamente la gestión del estado del servidor como un problema distinto de la gestión del estado del cliente, y señala que una vez que el estado del servidor pasa a React Query, cualquier estado del cliente que quede por gestionar a nivel global puede reducirse drásticamente.

Es ahí donde las cosas comienzan a resultar realmente interesantes desde el punto de vista arquitectónico.

Podrías llegar a una estructura como esta:

Client State
theme
sidebar
selectedTab
modal
editor
filters

Junto con:

Server State
users
projects
orders
notifications
products
analytics

En ese momento, las elecciones de herramientas se vuelven mucho más específicas:

Client State → Redux / Zustand / Context / React
Server State → TanStack Query

En lugar de esperar que un único almacén asuma ambas tareas al mismo tiempo.

Pero Redux todavía tiene una función

Considera otro tipo de aplicación, algo más similar a una herramienta de diseño como Figma.

Podrías terminar almacenando estados como:

{
  selectedLayer,
  activeTool,
  zoom,
  canvasMode,
  dragState,
  undoStack,
  redoStack
}

Nada de eso es estado del servidor.

El frontend lo controla por completo.

Cambia de forma síncrona, como respuesta directa a la interacción del usuario.

Varias partes de la interfaz dependen de él simultáneamente.

Puede ser necesario coordinar transiciones complejas cuando un estado afecta a otro.

Este es exactamente el tipo de escenario en el que un gestor dedicado de estado del cliente resulta útil.

La documentación de TanStack Query plantea un punto similar: el estado complejo, impulsado por la interfaz y sin relación con el servidor, sigue justificando el uso de una herramienta especializada para gestionar el estado del cliente.

Por lo tanto, la conclusión no es “eliminar Redux en todas partes y reemplazarlo por React Query”. Eso sería una corrección excesiva.

Y hay otro actor importante: RTK Query

Hay un detalle adicional que merece mención.

Redux Toolkit ya incluye RTK Query, una capa de obtención y almacenamiento en caché de datos diseñada específicamente para funcionar dentro de aplicaciones Redux.

Puede generar hooks automáticamente y encargarse de la obtención de datos desde endpoints, los estados de carga y el almacenamiento en caché, de manera similar a como lo hace TanStack Query.

Por lo tanto, el verdadero cambio que ocurre en el ecosistema no es simplemente:

Redux → React Query

Parece más bien esta progresión:

Manual API state in Redux
          ↓
Dedicated server-state solutions
          ↓
TanStack Query / RTK Query / Apollo / SWR

La industria en general está tomando gradualmente conciencia de que el estado del servidor y el estado del cliente son responsabilidades fundamentalmente diferentes que merecen herramientas distintas.

Una vez que internalizas esa distinción, tu arquitectura general de estado se vuelve considerablemente más fácil de comprender.

La regla que uso ahora

Al decidir si un dato pertenece al estado global, una sola pregunta resuelve la mayor parte del problema:

¿Quién es el dueño real de estos datos?

Si la respuesta es:

El backend

casi con certeza estás lidiando con el estado del servidor.

Si la respuesta es:

El frontend

casi con certeza estás lidiando con el estado del cliente.

Esa única pregunta suele orientarte hacia una arquitectura muy diferente según el caso.

Por ejemplo:

Current user ────────── Server
Projects ────────────── Server
Orders ──────────────── Server
Notifications ───────── Server
Theme ───────────────── Client
Modal ───────────────── Client
Selected tab ────────── Client
Editor state ────────── Client

Una vez que lo presentas de esa manera, la estructura adecuada se vuelve obvia.

React Query no está eliminando a Redux

Afirmar que “React Query está reemplazando a Redux” es un poco engañoso tal como se dice.

Lo que realmente está cambiando es algo más sutil:

Los desarrolladores están mejorando en identificar qué categoría de estado están manejando realmente.

Redux solía ser el destino por defecto para todo, incluidas las respuestas del servidor.

Cada vez más, los equipos están separando:

Server state
        ↓
TanStack Query

de:

Client state
        ↓
Redux / Zustand / Context / React

En muchos proyectos modernos de React, esa separación por sí sola elimina una parte sorprendente de la complejidad que antes se atribuía a Redux mismo.

El objetivo no es minimizar la cantidad de bibliotecas que se utilizan.

El objetivo es dejar de crear manualmente infraestructura para problemas que ya cuentan con abstracciones sólidas y diseñadas específicamente para ellos.

Así que la próxima vez que te encuentres con un slice de Redux sobrecargado lleno de respuestas de API, indicadores de carga, lógica de invalidación de caché y acciones de recarga, pregúntate:

¿Realmente necesitas un gestor de estado global para esto, o simplemente has reimplementado React Query a mano dentro de Redux?

¿Cómo estás manejando esto en tus aplicaciones?

¿Actualmente almacena los datos del servidor dentro de Redux o Zustand, depende de TanStack Query, o utiliza algún otro enfoque completamente diferente?

Lecturas relacionadas

  • Comprendiendo los ganchos personalizados de React: reutilización de lógica sin estado compartido — Aprenda qué son los ganchos personalizados de React, cómo extraen y comparten lógica con estado entre componentes, y qué errores comunes evitar al crearlos.
  • Power Apps vs React: comparación de costos a largo plazo y arquitectura — Este artículo analiza los costos de licenciamiento ocultos, las compensaciones arquitectónicas y las realidades de gestión que determinan si Power Apps o React son realmente más económicos a gran escala.
  • Reconsiderando el estado de React: dónde realmente deberían estar tus datos — Este artículo explica cómo reducir los errores en React al mover el estado a la URL, al DOM o a valores derivados, en lugar de usar excesivamente useState.