Hook useState
Gestiona el estado local del componente con el hook más fundamental de React.
Busca en todas las páginas de la documentación
Gestiona el estado local del componente con el hook más fundamental de React.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
const [value, setValue] = useState<T>(initialValue)
// Con inicializador perezoso (cálculo costoso)
const [value, setValue] = useState(() => computeExpensive())
// Función actualizadora (cuando el nuevo estado depende del anterior)
setValue(prev => prev + 1)Cuándo usarlo: Necesitas estado local y síncrono en un único componente.
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<div className="flex items-center gap-4">
<button
onClick={() => setCount(prev => prev - 1)}
className="px-3 py-1 border rounded"
>
-
</button>
<span className="text-xl font-mono w-12 text-center">{count}</span>
<button
onClick={() => setCount(prev => prev + 1)}
className="px-3 py-1 border rounded"
>
+
</button>
</div>
);
}Lo que esto demuestra:
useState básico con un númeroprev => prev + 1 en lugar de setCount(count + 1) para evitar problemas de cierre obsoletocount cambiauseState devuelve una tupla: el valor de estado actual y una función setterinitialValuesetState dentro del mismo manejador de eventos en un único re-renderizado para el rendimiento| Parámetro | Tipo | Descripción |
|---|---|---|
initialValue | T o () => T | Valor de estado inicial, o una función que lo devuelve (inicializador perezoso) |
| Retorno | Tipo | Descripción |
|---|---|---|
value | T | Valor de estado actual |
setValue | (value: T) => void o (prev: T) => T | Actualizador de estado - acepta un nuevo valor o una función actualizadora |
Estado de objeto:
const [form, setForm] = useState({ name: "", email: "" });
// Debe expandir para crear nueva referencia
setForm(prev => ({ ...prev, name: "Alice" }));Estado de array:
const [items, setItems] = useState<string[]>([]);
setItems(prev => [...prev, "new item"]);Inicializador perezoso (se ejecuta solo al montar):
const [data, setData] = useState(() => {
return JSON.parse(localStorage.getItem("key") ?? "null");
});// El tipo se infiere del valor inicial
const [count, setCount] = useState(0); // number
// Genérico explícito para tipos unión o null
const [user, setUser] = useState<User | null>(null);
// Genérico explícito para tipos complejos
const [items, setItems] = useState<Item[]>([]);Cosas que te afectarán. Cada trampa incluye qué sale mal, por qué sucede y la solución.
Trampa de cierre obsoleto - Leer count dentro de setTimeout o useEffect sin agregarlo al array de dependencias te da el valor antiguo. Solución: Usa la función actualizadora setCount(prev => prev + 1).
Identidad del objeto - setState({ ...obj }) crea una nueva referencia cada vez, incluso si los valores no han cambiado, causando re-renderizados innecesarios. Solución: Solo expande cuando los valores realmente cambien, o usa useMemo para valores derivados.
Trampa del inicializador perezoso - Pasar computeExpensive() en lugar de () => computeExpensive() ejecuta la función en cada renderizado, no solo en el primero. Solución: Siempre envuelve cálculos costosos en una función de flecha.
Trampa de agrupamiento - Llamar a setCount(count + 1) tres veces seguidas resulta en solo +1, no +3, porque cada llamada lee el mismo count. Solución: Usa la función actualizadora setCount(prev => prev + 1).
Otras formas de resolver el mismo problema - y cuándo cada una es la mejor opción.
| Alternativa | Úsalo cuando | No lo uses cuando |
|---|---|---|
useReducer | Las transiciones de estado son complejas o dependen del estado anterior | Toggle simple o valor único |
| Zustand store | El estado se comparte entre muchos componentes no relacionados | El estado es local a un componente |
| Parámetros de búsqueda de URL | El estado debe sobrevivir a la actualización de la página y ser compartible | Actualizaciones de alta frecuencia (escribir, arrastrar) |
useRef | Necesitas un valor mutable que no dispare re-renderizados | Necesitas que la UI refleje el valor |
¿Por qué no usar siempre Zustand? Zustand añade una dependencia e indirección. useState es de costo cero para el estado local - sin provider, sin store, sin selectores. Usa la herramienta más simple que funcione.
De una aplicación SaaS de producción Next.js 15 / React 19 (SystemsArchitect.io).
// Ejemplo de producción: formulario de edición de FAQ con múltiples useState
// Archivo: src/components/admin/faq-edit-form.tsx
'use client';
import { useState } from 'react';
interface FaqEditFormProps {
faq: Faq & { category?: { id: string; slug: string; title: string } };
onSave: (updatedFaq: Partial<Faq>) => Promise<void>;
onCancel: () => void;
}
export default function FaqEditForm({ faq, onSave, onCancel }: FaqEditFormProps) {
const [question, setQuestion] = useState(faq.question);
const [answer, setAnswer] = useState(faq.answer);
const [isActive, setIsActive] = useState(faq.isActive);
const [sortOrder, setSortOrder] = useState(faq.sortOrder);
const [isSaving, setIsSaving] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSaving(true);
try {
await onSave({ question, answer, isActive, sortOrder });
} catch (error) {
console.error('Error saving FAQ:', error);
} finally {
setIsSaving(false);
}
};
// ... JSX del formulario
}Lo que esto demuestra en producción:
useState. No se necesita genérico explícito para useState(faq.question) ya que faq.question ya está tipado como string.finally asegura que isSaving se reinicie a false tanto en éxito como en fallo. Sin él, un guardado fallido dejaría el formulario atrapado en un estado de carga.Partial<Faq> significa que solo los campos cambiados se envían al manejador de guardado, no el objeto FAQ completo. Esto mantiene la llamada API eficiente.useState separadas funcionan bien para un formulario de este tamaño. Para formularios con más de 6-8 campos, considera useReducer o una librería de formularios como react-hook-form para reducir boilerplate y habilitar validación a nivel de campo.isSaving se usa para desactivar el botón de envío durante la operación asíncrona, evitando envíos duplicados.setCount(count + 1) múltiples veces en el mismo manejador de eventos, cada llamada lee el mismo valor count obsoleto.prev => prev + 1 siempre recibe el estado pendiente más reciente, por lo que tres llamadas resultan en +3 en lugar de +1.setTimeout, useEffect y funciones asincrónicas.useState: useState(() => expensiveComputation()).localStorage o analizar datos grandes.setState dentro del mismo manejador de eventos en un único re-renderizado.setTimeout, promesas y manejadores de eventos nativos (agrupamiento automático).setA(1); setB(2); resulta en un re-renderizado, no dos.const [form, setForm] = useState({ name: "", email: "" });
// Correcto: expande para crear una nueva referencia
setForm(prev => ({ ...prev, name: "Alice" }));
// Incorrecto: mutar el objeto existente
form.name = "Alice"; // Sin re-renderizadoObject.is para comparar el estado antiguo y el nuevo.{ ...obj }), creas una nueva referencia, que dispara un re-renderizado incluso si los valores son idénticos.useState(computeExpensive()) llama a la función en cada renderizado y usa el resultado solo en el primero.useState(() => computeExpensive()) llama a la función solo en el primer renderizado.// Usa un genérico explícito para tipos unión
const [user, setUser] = useState<User | null>(null);
// Después, TypeScript sabe que user puede ser null
if (user) {
console.log(user.name); // estrechado a User
}// Sin el genérico, TypeScript infiere never[]
const [items, setItems] = useState<string[]>([]);
// Ahora puedes agregar strings
setItems(prev => [...prev, "new item"]);useState para valores simples e independientes (un toggle, un contador, una entrada única).useReducer cuando tienes 3+ valores de estado relacionados, o cuando el estado siguiente depende tanto del estado actual como de un payload de acción.useReducer o una librería de formularios reduce boilerplate.setState durante el renderizado programa un nuevo renderizado, que llama a setState nuevamente, creando un bucle infinito.useState son más simples y evitan expansiones innecesarias cuando solo un valor cambia.{ x, y }).useReducer en lugar de cualquiera de los dos enfoques.Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥