La regla práctica: minimiza la fuente, maximiza lo derivado
El nivel se cierra con la heurística que resume todo lo anterior: el estado fuente es un pasivo que hay que minimizar, y la derivación es el activo que hay que maximizar. Cada dato que guardas de más es un invariante que mantener y un bug futuro esperando. El catálogo de señales de que guardas demasiado —estado sincronizado por efectos, datos duplicados, booleanos contradictorios, el objeto en vez del id— y las excepciones legítimas donde lo que parece derivado es en realidad un hecho registrado.
Todo el nivel converge en una sola heurística, tan simple de enunciar como difícil de sostener bajo presión: minimiza el estado fuente y maximiza el derivado. El estado que almacenas es un pasivo —cada dato guardado es un invariante que mantener, una copia que sincronizar, un camino de mutación que no olvidar—, mientras que la derivación es un activo que se paga una vez y no puede quedar obsoleto. Cuando dudes entre guardar un dato o calcularlo, la respuesta por defecto es calcularlo, y solo guardas lo que no puedes deducir de nada más. Esta lección de cierre no introduce un primitivo nuevo: destila las cuatro anteriores en una disciplina de diseño, un catálogo de olores que delatan que guardas de más, y el reconocimiento honesto de las pocas excepciones donde lo que parece derivado es, en realidad, un hecho que debes registrar.
- Adoptar la regla de minimizar el estado fuente y maximizar el derivado como decisión por defecto.
- Aplicar la prueba del render para decidir, dato a dato, si algo debe almacenarse o calcularse.
- Reconocer el catálogo de señales de que guardas de más y su refactor correspondiente.
- Distinguir la excepción legítima: cuándo un valor que parece derivado es un hecho registrado.
La regla y su prueba
La regla se opera con una pregunta que aplicas a cada candidato a estado, y que ya usamos en la primera lección con otra forma: ¿puedo calcular este valor a partir de otro estado o de las props, durante el render? Si la respuesta es sí, no lo guardes; derívalo. Solo cuando la respuesta es no —cuando el dato es una verdad primitiva que nada más determina— merece ser estado fuente. Esta prueba del render es más estricta que la intuición, porque descubre como derivados muchos datos que instintivamente guardaríamos: recuentos, totales, banderas de validez, listas filtradas, el elemento seleccionado. El objetivo es que tu conjunto de estado fuente sea el mínimo del que todo lo observable pueda reconstruirse, y ni un dato más.
flowchart TD
Q[tengo un dato candidato a estado] --> C{puedo calcularlo de otro estado o de las props}
C -->|si| D[no lo guardes, derivalo en el render]
C -->|no| H{debe congelarse en el tiempo como un hecho}
H -->|si| F[es fuente, un hecho registrado]
H -->|no| S[es fuente, una verdad primitiva]
style D fill:#a6e3a1,color:#11111b
style F fill:#89b4fa,color:#11111b
style S fill:#89b4fa,color:#11111bRecorre la declaración de estado de un componente y, por cada campo, pregúntate en voz alta si podrías borrarlo y recomputarlo durante el render sin perder información. Los que sobreviven a esa pregunta son tu estado fuente real; los que no, son derivados disfrazados de estado. Este ejercicio, hecho con honestidad, suele reducir el estado de un componente a la mitad y elimina de golpe una familia entera de bugs de sincronización.
Las señales de que guardas de más
Ciertos patrones delatan, casi sin excepción, un estado que debería ser derivado. Aprender a olerlos convierte la regla en un reflejo. El más elocuente es un useEffect cuya única misión es actualizar un trozo de estado a partir de otro: ese efecto no es lógica, es el síntoma de que dos estados están acoplados y uno debería derivarse del otro. Le sigue de cerca el estado que siempre se escribe junto a otro —dos setters que nunca se llaman por separado suelen ser una fuente y sus derivados mal separados—, y la duplicación pura: la misma entidad guardada en dos sitios, condenada a divergir.
Sincronizado por un efecto
Un efecto cuyo cuerpo solo pone al día un estado a partir de otro. El estado que persigue al primero debería derivarse, no almacenarse ni sincronizarse.
Datos duplicados
La misma entidad viviendo en dos lugares. Normaliza: guarda una vez, por id, y deriva las demás vistas de esa única copia canónica.
Booleanos contradictorios
cargando, error y datos como banderas sueltas que pueden afirmar cosas incompatibles a la vez. Colápsalos en un único estado que las derive.
El objeto en vez del id
Guardar el elemento seleccionado entero en lugar de su id. El objeto se deriva del id más la colección; guardarlo lo condena a quedar obsoleto.
El caso de los booleanos contradictorios merece detenerse, porque conecta con el siguiente track. Tres banderas independientes para un mismo proceso permiten representar estados imposibles —cargando y con error a la vez, o con datos y aún cargando— y cada combinación imposible es un bug esperando a ocurrir. La solución es reducir esas banderas a un único estado fuente del que todas se derivan; llevado al extremo, ese estado único con transiciones explícitas es una máquina de estados, el tema del nivel siguiente.
// Antipatron: banderas sueltas que pueden contradecirse
const [cargando, setCargando] = useState(false);
const [error, setError] = useState(null);
const [datos, setDatos] = useState(null); // cargando y error pueden ser true a la vez
// Fuente minima: un solo estado; las banderas se derivan y no pueden mentir
const [estado, setEstado] = useState({ tipo: "inactivo" });
const cargando = estado.tipo === "cargando";
const hayError = estado.tipo === "error";
El patrón del identificador frente al objeto es igual de recurrente. Guardar el objeto seleccionado lo duplica —ya vive en la colección— y lo congela: si la colección se actualiza, tu copia seleccionada queda vieja. Guardar solo el id como fuente y derivar el objeto de la colección elimina ambos problemas de un plumazo, y es el mismo principio que sostiene la normalización del estado de servidor, donde guardas entidades por id y derivas cada vista de esa única copia.
// Antipatron: guardar el objeto seleccionado, que se duplica y envejece
const [seleccionado, setSeleccionado] = useState(null);
// Fuente minima: guardar el id; el objeto se deriva de la coleccion viva
const [seleccionadoId, setSeleccionadoId] = useState(null);
const seleccionado = items.find((i) => i.id === seleccionadoId) ?? null;
Si tuvieras que memorizar una sola señal de alarma, que sea esta: todo useEffect cuyo cuerpo se limita a llamar a un setter con un valor calculado a partir de otro estado o de las props es casi siempre estado derivado mal modelado. No estás reaccionando a nada externo —una red, un temporizador, el DOM—, estás recalculando a mano lo que el render podría calcular solo. Borra el efecto, borra el estado que mantenía, y sustitúyelos por una constante derivada. La documentación de React reúne estos casos bajo la idea de que quizá no necesitas un efecto, y casi siempre tiene razón.
Las excepciones legítimas
La regla es un valor por defecto potente, no un absolutismo, y un ingeniero maduro reconoce sus fronteras. La excepción más importante no es técnica sino conceptual: a veces, lo que parece derivado es en realidad un hecho que debe congelarse en el tiempo. El total de una factura en el momento de la compra no es una derivación de los precios actuales de los productos; es un dato histórico que debe permanecer inmutable aunque esos precios cambien mañana. Calcularlo al vuelo desde los precios vigentes sería, de hecho, un bug: reescribiría el pasado. Aquí el valor es estado fuente pese a su apariencia derivada, porque su verdad quedó fijada en un instante y no debe seguir a sus antiguas entradas.
La otra frontera es de rendimiento, y es más rara de lo que se cree. Cuando una derivación es tan cara que ni la memoización ni el corte por igualdad la hacen viable bajo demanda —un agregado sobre un volumen enorme que se consulta constantemente—, puede justificarse materializar el resultado como una copia denormalizada. Pero es una decisión que se toma con datos del perfilador, no por intuición, y que asume conscientemente la deuda de mantener ese caché coherente. Confundir esta excepción medida con la comodidad de tener el valor a mano es el origen de la mayoría de las duplicaciones que la regla combate.
Herramientas como TanStack Query encarnan la regla llevada al servidor. No guardas cargando, error ni datos como estado propio: son proyecciones derivadas del estado de la consulta, que la librería administra como una cache. Intentar copiar la respuesta a un useState propio reintroduce exactamente la duplicación y la sincronización por efectos que este nivel enseña a evitar. El estado de servidor es una cache que se deriva, no una verdad que se posee.
Agrupar lo relacionado y otras derivaciones cotidianas
Dos correcciones más cierran el catálogo. La primera es agrupar el estado relacionado: cuando varios trozos de estado cambian siempre juntos —las coordenadas de un puntero, los campos de un mismo formulario—, mantenerlos como estados sueltos invita a actualizarlos por separado y a que se desincronicen; reunirlos en un solo objeto de estado convierte esa coherencia en algo estructural. La segunda, ya vista como espejo de props, es derivar en vez de copiar cuando el dato proviene de las props o de la URL.
Precisamente la URL es una de las fuentes más infrautilizadas. El término de búsqueda, la página actual, el filtro activo: si viven en la URL como fuente, el estado de tu componente se deriva de ella —queda compartible, navegable con el botón de atrás y recargable— en lugar de duplicarse en un useState que hay que sincronizar con la barra de direcciones. Lo mismo vale para los formularios, donde el valor de un campo es fuente, pero su validez, sus errores y si está sucio son derivaciones que no deben almacenarse.
// La URL como fuente: el estado de la vista se deriva, no se duplica
const params = new URLSearchParams(location.search);
const busqueda = params.get("q") ?? ""; // fuente: la URL
const pagina = Number(params.get("p") ?? 1); // fuente: la URL
// nada de esto se copia a useState; cambiarlo es navegar, no hacer setState
// En un formulario, el valor es fuente; validez y errores se derivan
const [valores, setValores] = useState({ email: "" });
const errores = validar(valores); // derivado, jamas almacenado
const esValido = Object.keys(errores).length === 0; // derivado del derivado
El catálogo completo cabe en una tabla que puedes usar como lista de revisión: a la izquierda, el olor; a la derecha, la derivación que lo disuelve.
| Olor de que guardas de más | Refactor |
|---|---|
| Efecto que sincroniza estado con estado | Deriva en el render y borra el efecto |
| La misma entidad en dos sitios | Normaliza por id y deriva las vistas |
cargando, error y datos sueltos |
Un estado único del que se derivan |
| El objeto seleccionado guardado | Guarda el id, deriva el objeto |
| Una prop copiada a estado | Deriva de la prop o reinicia con key |
| Recuento o total almacenado | Derívalo de la colección en el render |
| Validez y errores de formulario guardados | Derívalos de los valores de los campos |
Antes de crear un useState, pregúntate si el dato no vive ya en algún sitio del que podrías derivarlo: la URL, la cache de una consulta al servidor, las props, otro estado. Mucho estado local no es más que una copia mal sincronizada de una fuente que ya existía en otra capa. El estado más fácil de mantener coherente es el que nunca creaste, porque lo derivaste de donde ya estaba.
Si un nivel entero sobre derivación se pudiera comprimir en una frase, sería esta: el estado que guardas es una responsabilidad, no una posesión, y el buen diseño consiste en tener la menor cantidad posible de él. Cada dato en tu estado fuente es un contrato contigo mismo —me comprometo a mantener esto coherente con todo lo demás, por todos los caminos de mutación, para siempre— y ese contrato se incumple exactamente donde no miras. La derivación es la técnica de firmar el menor número de esos contratos: representas cada verdad una sola vez, en su forma más irreducible, y todo lo demás lo calculas como una función de esa verdad, de modo que la coherencia deja de ser algo que mantienes y pasa a ser algo que es cierto por construcción. Por eso la pregunta que debe gobernar cada decisión de estado no es qué necesito mostrar —eso lleva a guardarlo todo— sino qué es lo mínimo irreducible de lo que todo lo demás se deduce. Cuando interiorizas esto, tu instinto se invierte: ante cada dato nuevo, tu primer impulso ya no es buscar dónde guardarlo, sino demostrar que no hace falta guardarlo porque se deriva de algo que ya tienes. Los cuatro escalones de este nivel encajan entonces como una sola idea vista desde ángulos distintos: derivar en lugar de duplicar es la regla; memoizar es hacer que derivar sea barato; el selector es derivar con la identidad bien cuidada; el grafo es derivar en cascada sin glitches ni trabajo de más. Y todo ello sirve a un fin que no es el rendimiento ni la elegancia, sino la corrección: un programa cuyo estado fuente es mínimo tiene menos invariantes que romper, menos copias que divergir, menos estados imposibles que representar, y por tanto categorías enteras de bugs que, sencillamente, no puede escribir. Minimizar la fuente y maximizar lo derivado no es una técnica más de gestión de estado; es la forma que adopta, en este dominio, el principio más antiguo de la ingeniería: no digas la misma verdad dos veces, porque el día que discrepen no sabrás cuál era la verdadera.
- Toma un componente o un store real, aplica la prueba del render campo a campo y elabora la lista de lo que es verdadera fuente frente a lo que es derivado disfrazado.
- Caza cada
useEffectque solo sincronice estado con estado y elimínalo junto al estado redundante que mantenía. - Convierte un trío de banderas —
cargando,error,datos— en un único estado del que se deriven, y comprueba que los estados contradictorios se vuelven irrepresentables. - Sustituye un objeto seleccionado almacenado por su
idy deriva el objeto de la colección; verifica que ya no puede quedar obsoleto al actualizarse la colección. - Encuentra en tu dominio un valor que parezca derivado pero deba congelarse como hecho histórico, y argumenta por qué, en su caso, guardarlo es lo correcto.