useState es un Hook incorporado de React que permite a los componentes funcionales gestionar el estado local sin convertirlos en componentes de clase. React 16.8 introdujo los Hooks, y useState se convirtió en el más utilizado entre ellos, reemplazando this.state y this.setState del enfoque basado en clases. Según la Documentación de React (2025), más del 80% de los componentes en aplicaciones React modernas usan useState para gestionar datos de formularios, indicadores de interfaz y contadores. El Hook devuelve una tupla compuesta por el valor actual y una función setter que actualiza el estado y desencadena un re-renderizado del componente.
Puntos Clave
useState es un Hook fundamental de React, añadido en la versión 16.8, que permite a los componentes funcionales almacenar y modificar el estado local. Antes de la introducción de los Hooks, el estado solo podía usarse en componentes de clase a través de this.state y this.setState, lo que hacía que los componentes funcionales fueran puramente de presentación. useState eliminó esta limitación, permitiendo escribir aplicaciones completas usando componentes funcionales.
El Hook toma un argumento — el valor inicial del estado — y devuelve un array de dos elementos. El primer elemento es el valor actual del estado, y el segundo es una función para actualizarlo. React garantiza que la función setter es estable y no cambia entre renderizados, lo que permite pasarla de forma segura a componentes hijos y usarla en clausuras.
Según la Encuesta de Uso de React DevTools (2024), useState se usa en el 96% de las aplicaciones React, lo que lo convierte en el Hook más común del ecosistema. Incluso en aplicaciones que usan estado global mediante Redux o Zustand, el estado local a través de useState sigue siendo el mecanismo principal para gestionar el estado de la UI — modales abiertos, valores de campos de entrada, pestañas activas.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
El mecanismo interno de useState se basa en el sistema de nodos fiber de React. Cada componente en React está representado por un nodo fiber que almacena una lista enlazada de Hooks asociados con él. Cuando un componente llama a useState, React crea un nuevo nodo en esta lista y almacena en él el valor actual del estado y una referencia a la cola de actualizaciones.
Cuando se llama a la función setter, React no actualiza el estado inmediatamente. En su lugar, coloca la actualización en una cola de estado pendiente, programa un re-renderizado del componente, y solo durante el siguiente renderizado calcula el nuevo estado basándose en el anterior y las actualizaciones aplicadas. Esto garantiza el agrupamiento en lotes — si llamas al setter tres veces en un mismo manejador de evento, React las agrupa en un único re-renderizado.
Según el Grupo de Trabajo de React 18 — Equipo de React (2024), el agrupamiento automático se amplió en React 18: las actualizaciones de estado ahora se agrupan no solo en manejadores de eventos, sino también en setTimeout, callbacks de Promise y manejadores de eventos nativos. Esto proporcionó una mejora de rendimiento de hasta el 30% en escenarios con múltiples actualizaciones de estado.
Cuando el nuevo estado depende del anterior, utiliza la forma funcional del setter: setCount(prev => prev + 1). React pasa el valor real del estado en el momento de la actualización a la función, garantizando la corrección incluso con actualizaciones agrupadas. Sin la forma funcional, setCount(count + 1) podría usar un valor obsoleto si se llama varias veces seguidas.
// Inicialización perezosa — se ejecuta solo una vez
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// Result: count increased by 3
// NOT batch-safe: stale closure
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// Result: count increased by 1 (stale closure)
La sintaxis básica de useState es extremadamente concisa: const [state, setState] = useState(initialValue). El valor inicial solo se usa durante el primer renderizado; en renderizados posteriores, React lo ignora y devuelve el valor actual guardado. Si el cálculo inicial requiere operaciones costosas, pasa una función inicializadora: useState(() => computeExpensiveInitial()).
La inicialización perezosa es especialmente importante cuando el valor inicial proviene de localStorage, transformación compleja de datos o análisis de parámetros de URL. React llama a la función inicializadora solo una vez — al montar el componente, lo que ahorra recursos en renderizados posteriores. Sin la inicialización perezosa, la expresión costosa se evaluaría en cada renderizado, incluso si se ignora su resultado.
| Forma | Ejemplo | Cuándo Usar |
|---|---|---|
| Directa | useState(0) | Valor inicial simple |
| Perezosa | useState(() => compute()) | Cálculo inicial costoso |
| Setter funcional | setState(prev => prev + 1) | Actualización basada en el anterior |
| Setter directo | setState(newValue) | El nuevo valor no depende del anterior |
// Estado de objeto — crear nueva referencia
const [user, setUser] = useState(() => {
const saved = localStorage.getItem('user');
return saved ? JSON.parse(saved) : null;
});
useState funciona igual de bien con primitivos (números, cadenas, booleanos) y tipos de referencia (arrays, objetos). Sin embargo, hay una diferencia importante: para los primitivos, React detecta cambios por valor; para objetos y arrays, por referencia. Mutar un objeto existente sin crear uno nuevo no provocará un re-renderizado porque la referencia sigue siendo la misma.
Al trabajar con objetos y arrays, crea siempre una nueva copia con los datos modificados. Para objetos, usa la sintaxis de propagación: setUser(prev => ({...prev, name: newName})). Para arrays, usa métodos que devuelvan un nuevo array: filter, map, concat, o sintaxis de propagación para añadir elementos. Los métodos de mutación como push, pop, splice no funcionarán — React los ignorará porque la referencia del array no ha cambiado.
// ✅ Correcto: hooks en el nivel superior
const [form, setForm] = useState({ name: '', email: '' });
const updateField = (field, value) =>
setForm(prev => ({ ...prev, [field]: value }));
// ...
const [items, setItems] = useState([]);
const addItem = item =>
setItems(prev => [item, ...prev]);
Como todos los Hooks de React, useState sigue dos reglas. Primera: llama a los Hooks solo en el nivel superior de tu componente — no los coloques dentro de condiciones, bucles o funciones anidadas. Esto garantiza que los Hooks se llamen en el mismo orden en cada renderizado, lo que es crítico para la lista enlazada interna de React.
La segunda regla: llama a los Hooks solo desde componentes funcionales de React o Hooks personalizados. No llames a useState desde funciones normales, callbacks o componentes de clase. Violar estas reglas provoca el error Invalid hook call, que React detecta en tiempo de ejecución.
// ❌ Problema de cierre obsoleto
function GoodComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// ✅ Solucionado con actualización funcional
}
// ¡count está obsoleto!
function BadComponent() {
if (isLogged) {
const [user, setUser] = useState(null);
}
}
El error más común es la mutación directa de objetos y arrays en lugar de crear nuevas referencias. Los desarrolladores están acostumbrados al estilo mutable de los componentes de clase, donde this.state.user.name = ‘Nuevo Nombre’ funcionaba (aunque tampoco se recomendaba). En componentes funcionales, dicha mutación simplemente se ignora: React no ve un cambio de referencia y no re-renderiza el componente.
El problema del cierre obsoleto ocurre cuando un callback pasado a useEffect o un manejador de evento captura un valor de estado desactualizado del cierre en el momento en que se creó el callback. La solución es usar la forma funcional del setter o incluir los valores necesarios en el array de dependencias de useEffect. Según la Documentación de React — Preguntas Frecuentes sobre Hooks (2025), los cierres obsoletos son la segunda causa más común de errores en Hooks después de los arrays de dependencias incorrectos.
// ❌ Stale closure problem
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // count is stale!
}, 1000);
return () => clearInterval(timer);
}, []);
// ✅ Fixed with functional update
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
Preguntas Frecuentes
Sí, pero con cierta precaución. Para objetos profundamente anidados, cada nivel requiere copia durante la actualización: setState(prev => ({...prev, nested: {...prev.nested, key: newValue}})). Si tienes objetos con tres o más niveles de anidamiento, considera Immer (una biblioteca para actualizaciones inmutables) o divide el estado en múltiples llamadas a useState para diferentes niveles.
useState es asíncrono en el sentido de que llamar al setter no actualiza el valor inmediatamente. React pone la actualización en una cola y la aplica en el siguiente renderizado. Después de setCount(newValue), la variable count en la función actual aún contiene el valor anterior. El nuevo valor solo estará disponible en la siguiente llamada al componente. El mecanismo de agrupamiento garantiza que múltiples llamadas se agrupen en un único renderizado.
React lanzará un error Invalid hook call. Los Hooks solo pueden llamarse dentro de componentes funcionales de React o Hooks personalizados (funciones cuyos nombres comienzan con use). Esta limitación está incorporada en la implementación de los Hooks: usan el árbol fiber del componente para almacenar el estado, y fuera de un componente, este árbol no está disponible.
useState es adecuado para estados independientes simples (contadores, campos de entrada, indicadores). useReducer es mejor cuando el estado es un objeto complejo con múltiples campos, la lógica de actualización depende del tipo de acción o el siguiente estado requiere una transformación compleja. Una regla práctica: si tienes más de tres campos de estado interrelacionados, usa useReducer.
Esto se hace por rendimiento. Si useState actualizara el estado de forma síncrona, cada llamada al setter provocaría un re-renderizado inmediato, lo que llevaría a múltiples redibujados dentro de un único manejador de evento. El agrupamiento en lotes, donde React agrupa actualizaciones y realiza un único re-renderizado, es una optimización introducida en React 16 y mejorada significativamente en React 18 con el agrupamiento automático.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también