Por qué surgió la ola ligera: el peso de la ceremonia
Durante casi una década, estado global en React significó Redux, y significarlo tanto que la pregunta dejó de hacerse. La ola ligera de mediados de los veinte no nació de un defecto técnico —Redux era y sigue siendo un diseño excelente— sino de un desajuste: una herramienta que cobraba una ceremonia proporcional a un problema grande y general, aplicada a la mayoría de apps que tenían un problema pequeño y particular. Reconstruimos ese desajuste y el hueco que dejó abierto.
Durante casi una década, decir estado global en React fue decir Redux, y se dijo tanto que la pregunta dejó de formularse: se instalaba por defecto, como quien pone sal al agua. La ola ligera que lo desplazó no nació de un fallo técnico —el diseño de Redux era y es excelente— sino de un desajuste más sutil y más interesante: la herramienta resolvía un problema grande y general con una ceremonia proporcional a ese problema, mientras la mayoría de las aplicaciones tenían un problema pequeño y particular. Este nivel es la historia de ese desajuste y de las tres respuestas —Zustand, Jotai, Valtio— que vinieron a ocupar el hueco.
- Reconstruir por qué Redux se volvió el default y qué problema resolvía de verdad.
- Anatomizar la ceremonia:
actions, creadores,reducers,connecty la inmutabilidad a mano. - Entender por qué
Redux Toolkitrecortó el boilerplate pero no el peso ni el modelo mental. - Situar el hueco —el estado global simple— que la ola ligera vino a llenar.
Flux: la arquitectura que ganó una guerra real
Para entender por qué Redux se impuso hay que recordar el caos del que salvó. Hacia 2015, las SPA grandes sufrían el problema del data binding bidireccional: la mutación del estado estaba dispersa por decenas de componentes, cualquiera podía escribir en cualquier sitio, y reconstruir cómo se había llegado a un estado inconsistente era una arqueología desesperante. Flux, y su encarnación canónica Redux, impuso una disciplina severa y liberadora: un único árbol de estado inmutable, una sola vía de cambio —despachar una acción que un reducer puro transforma en un estado nuevo— y un flujo estrictamente unidireccional.
flowchart LR V[vista] -->|dispatch action| A[accion] A --> R[reducer puro] R -->|estado nuevo| S[store unico] S -->|selector| V
Conviene fecharlo para no idealizarlo. Redux nació en 2015, presentado por Dan Abramov, destilando ideas de la arquitectura Flux de Facebook y del patrón de actualización de Elm. Su éxito fue tan rotundo que en pocos años se volvió sinónimo de gestión de estado, hasta el punto de que muchos desarrolladores aprendían React y Redux como si fueran una sola tecnología inseparable. Esa fusión en la mente colectiva es parte del problema que este nivel desanda: Redux dejó de percibirse como una elección entre varias y pasó a sentirse como una parte constitutiva de React, cuando nunca lo fue.
Esa disciplina compraba tres bienes escasos: previsibilidad —el estado solo cambia por acciones nombradas—, trazabilidad —cada cambio es un evento con registro— y el legendario time-travel debugging de las devtools, que permite rebobinar la aplicación acción a acción. La renuncia deliberada a mutar donde uno quisiera fue precisamente lo que compró esos bienes: a cambio de la ceremonia, Redux devolvía el control. Para un equipo grande sobre una app compleja, esto no era ceremonia gratuita, era la diferencia entre un sistema auditable y uno ingobernable. Redux ganó porque resolvía un problema real y lo resolvía bien.
Merece recordarse esto antes de criticarlo, porque la crítica fácil olvida el contexto. En su momento, adoptar Redux era la decisión sensata: no existían alternativas ligeras maduras, la Context API de React era rudimentaria y propensa a re-renders masivos, y el estado de servidor no tenía aún herramienta propia. Redux no se impuso por inercia sino por mérito; lo que cambió más tarde no fue su calidad, sino el paisaje de opciones y la naturaleza del problema a su alrededor. Juzgarlo hoy por el desajuste con la app mediana de 2026 sin recordar el vacío que llenó en 2015 es un anacronismo.
Anatomía de la ceremonia
El precio de esa disciplina era el boilerplate. Sumar uno a un contador exigía coordinar varias piezas repartidas en varios archivos, cada una un pequeño impuesto:
// El contador clasico: cinco piezas conceptuales para sumar uno.
const INCREMENT = 'counter/increment'
function increment() {
return { type: INCREMENT }
}
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENT:
return { ...state, value: state.value + 1 }
default:
return state
}
}
Y eso era solo el estado. Para conectarlo a un componente hacía falta el Provider en la raíz y el arnés de connect, con sus dos funciones de mapeo:
const mapState = (state) => ({ value: state.counter.value })
const mapDispatch = { increment }
export default connect(mapState, mapDispatch)(Counter)
Cada capa tenía su justificación en el problema grande, pero conviene nombrarlas una a una para ver que ninguna era gratis:
El tipo de acción
Una constante que identifica el evento. Existe para que la acción sea serializable y el time-travel pueda registrarla y reproducirla fielmente.
El creador de acción
Una función que fabrica el objeto de acción. Centraliza la forma del evento, pero es una capa de indirección más que mantener y sincronizar.
El reducer puro
La función sin efectos que, dado estado y acción, devuelve estado nuevo. Es el corazón determinista, y el que obliga a la inmutabilidad a mano.
El selector
La función que extrae del árbol la porción que un componente necesita, para no re-renderizar de más sobre un store monolítico.
Ninguna de las cuatro es redundante en el problema grande; cada una compra una garantía concreta. El drama es que las cuatro se cobran juntas y por adelantado, sea tu estado diez mil líneas de dominio o un interruptor de tema. El coste es fijo; el beneficio, proporcional al tamaño del problema. Cuando el problema es pequeño, la relación se invierte y la herramienta cuesta más de lo que rinde.
Conviene ver esta desproporción como lo que es: un problema de calibración, no de calidad. Una báscula de precisión de laboratorio no es peor que una de cocina; es peor para pesar harina. Redux es esa báscula de laboratorio, exacta y auditable, aplicada durante años a pesar harina en la cocina de la app mediana. El instrumento no falla; falla el emparejamiento entre instrumento y tarea, y ese emparejamiento es una decisión que durante años no se tomaba.
Es tentador reducir la crítica a Redux al número de líneas, pero el boilerplate mecanografiado es lo de menos: un snippet lo genera. El coste real era el modelo mental. Para tocar una pieza de estado había que pensar en cuatro conceptos —accion, tipo, reducer, selector— y saltar entre tres archivos. Esa carga era invisible para quien ya la había interiorizado y brutal para quien llegaba nuevo, y es la que ninguna reducción de sintaxis eliminó del todo.
Un impuesto merece mención aparte por lo insidioso: la inmutabilidad a mano. Antes de Immer, actualizar un valor anidado significaba reconstruir con el operador de propagación cada nivel del objeto hasta la raíz, y un solo nivel olvidado producía o bien una mutación accidental que rompía el time-travel, o bien un bug de referencia que impedía el re-render. No era solo tedioso: era una fuente estructural de errores sutiles que solo desaparecía con disciplina constante.
Una forma útil de estimar la ceremonia de una herramienta no es contar líneas sino contar decisiones y saltos: cuántos archivos tocas y cuántos conceptos coordinas para hacer un cambio pequeño. En Redux clásico, sumar un campo al estado obligaba a tocar el tipo, el creador, el reducer y a veces el selector, repartidos en varios archivos. Ese número —los puntos que hay que mantener en coherencia— es mejor predictor del coste real que el recuento de líneas, y es el que la ola ligera bajó casi a uno.
El malentendido del estado de servidor
Hay un detalle histórico que explica media crítica a Redux: buena parte de lo que la gente metía en el store no era estado de cliente, sino datos del servidor. Resultados de fetch, con sus banderas de loading, error y data copiadas a mano en cada recurso, cacheados manualmente y revalidados a ojo. Redux hacía de caché de red, un trabajo para el que nunca fue diseñado, y lo hacía con enorme ceremonia porque cada endpoint reclamaba su tríada de acciones —petición, éxito, fracaso— y su porción de reducer.
La consecuencia de este malentendido fue doble y perversa. Por un lado, inflaba los stores con datos que cambiaban por su cuenta —el servidor manda una versión nueva y tu caché a mano queda obsoleta sin avisar—, lo que multiplicaba las acciones y los reducers dedicados a orquestar peticiones. Por otro, mezclaba dos naturalezas de estado radicalmente distintas: el de cliente, que la app posee y controla, y el de servidor, que la app solo refleja y nunca posee de verdad. Tratarlos con la misma maquinaria era la raíz de gran parte de la complejidad que se le atribuía a Redux pero que en rigor pertenecía a la ausencia de una herramienta de caché.
Cuando más tarde TanStack Query y compañía extrajeron el estado de servidor a su propia herramienta especializada —tema del nivel siguiente en el track— gran parte del contenido de los stores de Redux se evaporó, y quedó a la vista lo que de verdad era estado global de cliente: sorprendentemente poco.
Este punto es tan central que conviene fijarlo antes de seguir: gran parte de la percepción de que React necesitaba una solución de estado pesada venía de resolver mal un problema —el de servidor— que en realidad pedía una herramienta distinta. Corrige ese error de categoría y la necesidad restante se encoge tanto que casi cualquier solución ligera basta. La ola ligera no fue solo una respuesta a Redux; fue en buena parte una consecuencia de haber separado por fin dos problemas que llevaban una década mezclados en el mismo store.
Redux Toolkit y el hueco que dejó
La comunidad no ignoró la queja. Redux Toolkit (RTK) se volvió la forma oficial y recortó el boilerplate de raíz: createSlice genera los creadores de acción y los tipos a partir de los reducers, e Immer permite escribir mutaciones aparentes que producen actualizaciones inmutables por debajo.
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counter = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 }, // Immer: mutas y produce inmutable
},
})
export const { increment } = counter.actions
export const store = configureStore({ reducer: { counter: counter.reducer } })
La mejora fue genuina y grande. createSlice elimina de un plumazo las constantes de tipo y los creadores de acción; Immer disuelve el impuesto de la inmutabilidad a mano; configureStore trae devtools y middleware sensato por defecto; y RTK Query, incluido en el paquete, por fin dio al estado de servidor una caché de primera clase en vez del patrón manual de acciones —una admisión implícita de que meter datos de red en reducers había sido siempre un uso forzado—. Para quien ya vivía en Redux, RTK fue un alivio enorme y es la forma correcta de escribirlo hoy.
Y sin embargo, la propia existencia de RTK encierra una ironía reveladora. Que Redux necesitara un toolkit oficial cuyo propósito declarado era reducir su boilerplate es una admisión, por parte de sus propios mantenedores, de que la ceremonia se había vuelto un problema real y reconocido. RTK es la mejor respuesta posible a esa autocrítica; pero una herramienta que necesita una segunda herramienta para volverse tolerable ya ha dicho algo sobre el tamaño de su superficie, y no todos los proyectos tenían por qué pagar esa superficie de entrada.
Pero no cerró el hueco, por dos motivos que no eran de sintaxis. Primero, el peso: RTK con react-redux ronda los 15KB comprimidos, un presupuesto difícil de justificar para guardar el usuario autenticado, el tema y un par de modales. Segundo, el modelo mental seguía intacto: Provider en la raíz, useSelector, useDispatch, y el marco conceptual de acciones y reductores que sigue siendo obligatorio aunque el problema sea trivial. Hay una tensión de fondo que RTK no podía resolver por diseño: su valor y su coste son la misma cosa. Las convenciones que hacen a Redux auditable en un equipo grande son exactamente las que lo vuelven pesado en un proyecto pequeño, y no se puede tener lo uno sin lo otro.
La lectura equivocada de esta historia es la del relato heroico: unas librerías valientes derrocaron a un tirano pesado. La lectura correcta es menos épica y más útil. Redux nunca fue malo; fue una herramienta calibrada para un problema grande —estado complejo, equipos grandes, necesidad de auditoría— que se aplicó por defecto a un universo de apps cuyo problema era pequeño. El desajuste no estaba en la herramienta sino en la ausencia de decisión: se elegía Redux sin preguntar si el problema lo pedía. Lo que la ola ligera cambió no fue la calidad disponible, sino la pregunta por defecto. Antes era cuánto Redux; después fue si hace falta Redux, y para la mayoría la respuesta honesta resultó ser no. Cuando el estado de servidor se marchó a TanStack Query y lo que quedó fue un puñado de valores globales simples, un framework de 15KB con cuatro conceptos era, para el problema mediano, usar un torno de banco para clavar una chincheta. Zustand, Jotai y Valtio no derrotaron a Redux: ocuparon el hueco que quedó cuando el problema se reveló, para la mayoría, mucho más pequeño de lo que Redux suponía. Y por eso el nivel se llama post-Redux y no anti-Redux: lo que sigue no niega a Redux, lo pone en su escala.
Cuidado con la inversión del argumento. Que Redux sea excesivo para la app mediana no lo vuelve un error para todas. En una base grande, con muchos desarrolladores, requisitos de trazabilidad y estado genuinamente complejo, RTK sigue siendo una elección defendible y a menudo la mejor. El pecado no es usar Redux: es usarlo sin haberse preguntado si el problema justifica su ceremonia. Ese es exactamente el mismo criterio que aplicaremos, invertido, a las alternativas ligeras: ninguna es un default nuevo al que rendirse sin pensar.
- Toma una app tuya con Redux o RTK y lista todo lo que vive en el store. Marca cada entrada como estado de servidor —datos de un
fetch— o estado de cliente —lo que la app inventa y posee. - Cuenta qué proporción es realmente estado de servidor. Ese porcentaje es candidato a salir del store hacia una caché de datos, no a otra librería de estado.
- De lo que quede como estado de cliente puro, pregúntate cuántos conceptos de Redux —accion, tipo, reducer, selector— usas de verdad y cuántos arrastras por inercia.
- Reescribe mentalmente una actualización anidada sin Immer, a mano con propagación, y cuenta los niveles que reconstruirías. Ese número es el impuesto de inmutabilidad que la ola ligera te ahorra o te automatiza.
- Estima el peso: cuánto pesa tu solución actual comprimida y cuánto de ese presupuesto sirve al estado que de verdad tienes. Guarda la cifra: la contrastaremos con los 3KB de
Zustanden la lección siguiente.