5. Estado en React (useState)
🔄 5.1 Concepto de estado (state)
Section titled “🔄 5.1 Concepto de estado (state)”¿Qué es el estado?
Section titled “¿Qué es el estado?”El estado (state) es un objeto que contiene datos que pueden cambiar durante la vida de un componente. Cuando el estado cambia, React re-renderiza el componente automáticamente.
| Característica | Descripción |
|---|---|
| Reactivo | Cambios en el estado actualizan la UI |
| Local | Cada componente tiene su propio estado |
| Mutable | Se puede modificar (a diferencia de props) |
| Persistente | Se mantiene entre renders |
Estado vs Variables normales
Section titled “Estado vs Variables normales”// ❌ Variable normal - NO funcionafunction Contador() {let contador = 0;
const incrementar = () => { contador++; // Cambia, pero React no lo sabe console.log(contador); // Muestra el valor correcto};
return ( <div> <p>Valor: {contador}</p> {/* Siempre muestra 0 */} <button onClick={incrementar}>+1</button> </div>);}
// ✅ Estado con useState - FUNCIONAimport { useState } from 'react';
function Contador() {const [contador, setContador] = useState(0);
const incrementar = () => { setContador(contador + 1); // React detecta el cambio};
return ( <div> <p>Valor: {contador}</p> {/* Se actualiza automáticamente */} <button onClick={incrementar}>+1</button> </div>);}¿Cuándo usar estado?
Section titled “¿Cuándo usar estado?”| Usar estado | No usar estado |
|---|---|
| Datos que cambian | Datos constantes |
| Inputs de formulario | Props recibidas |
| Contadores | Cálculos derivados |
| Datos de API | Valores estáticos |
| Toggle (mostrar/ocultar) | Configuración fija |
🪝 5.2 Hook useState
Section titled “🪝 5.2 Hook useState”Sintaxis de useState
Section titled “Sintaxis de useState”import { useState } from 'react';
function MiComponente() {// Sintaxis: const [valor, setValor] = useState(valorInicial);const [contador, setContador] = useState(0);
// ↑ ↑ ↑// valor función para valor// actual actualizar inicial
return <p>{contador}</p>;}Anatomía de useState
Section titled “Anatomía de useState”// useState retorna un array con 2 elementos:const resultado = useState(0);const valor = resultado[0]; // El valor actualconst setValor = resultado[1]; // La función para actualizarlo
// Usando desestructuración (forma común):const [valor, setValor] = useState(0);
// Ejemplos de nombres:const [nombre, setNombre] = useState("");const [edad, setEdad] = useState(18);const [activo, setActivo] = useState(true);const [items, setItems] = useState([]);const [usuario, setUsuario] = useState(null);Múltiples estados
Section titled “Múltiples estados”function Formulario() {// Puedes tener múltiples estadosconst [nombre, setNombre] = useState("");const [email, setEmail] = useState("");const [edad, setEdad] = useState(0);const [aceptaTerminos, setAceptaTerminos] = useState(false);
return ( <form> <input value={nombre} onChange={(e) => setNombre(e.target.value)} /> <input value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="number" value={edad} onChange={(e) => setEdad(Number(e.target.value))} /> <input type="checkbox" checked={aceptaTerminos} onChange={(e) => setAceptaTerminos(e.target.checked)} /> </form>);}🎯 5.3 Inicialización del estado
Section titled “🎯 5.3 Inicialización del estado”Valores iniciales
Section titled “Valores iniciales”// String vacíoconst [texto, setTexto] = useState("");
// Númeroconst [contador, setContador] = useState(0);
// Booleanoconst [visible, setVisible] = useState(true);
// Array vacíoconst [lista, setLista] = useState([]);
// Array con datosconst [frutas, setFrutas] = useState(["Manzana", "Banana"]);
// Objetoconst [usuario, setUsuario] = useState({nombre: "",email: "",edad: 0});
// null (para datos que vendrán después)const [datos, setDatos] = useState(null);Inicialización perezosa (lazy)
Section titled “Inicialización perezosa (lazy)”// ❌ Se ejecuta en CADA render (ineficiente)const [datos, setDatos] = useState(calcularDatosComplejos());
// ✅ Se ejecuta solo en el PRIMER render (eficiente)const [datos, setDatos] = useState(() => calcularDatosComplejos());
// Ejemplo prácticofunction App() {// Leer de localStorage solo una vezconst [tema, setTema] = useState(() => { const guardado = localStorage.getItem('tema'); return guardado || 'claro';});
return <div className={tema}>...</div>;}✏️ 5.4 Actualización del estado correctamente
Section titled “✏️ 5.4 Actualización del estado correctamente”Actualización básica
Section titled “Actualización básica”function Contador() {const [count, setCount] = useState(0);
return ( <div> <p>Contador: {count}</p>
{/* Actualización directa */} <button onClick={() => setCount(count + 1)}>+1</button> <button onClick={() => setCount(count - 1)}>-1</button> <button onClick={() => setCount(0)}>Reset</button> </div>);}Actualización funcional (recomendada)
Section titled “Actualización funcional (recomendada)”// ❌ Puede causar problemas con actualizaciones rápidasconst incrementar = () => {setCount(count + 1);setCount(count + 1); // Ambos usan el mismo valor de count// Resultado: solo incrementa 1};
// ✅ Actualización funcional - siempre usa el valor más recienteconst incrementar = () => {setCount(prev => prev + 1);setCount(prev => prev + 1); // Usa el valor actualizado// Resultado: incrementa 2};
// Ejemplo completofunction Contador() {const [count, setCount] = useState(0);
const incrementar = () => setCount(prev => prev + 1);const decrementar = () => setCount(prev => prev - 1);const duplicar = () => setCount(prev => prev * 2);
return ( <div> <p>{count}</p> <button onClick={incrementar}>+1</button> <button onClick={decrementar}>-1</button> <button onClick={duplicar}>x2</button> </div>);}📦 5.5 Estado con objetos
Section titled “📦 5.5 Estado con objetos”Actualizar objetos correctamente
Section titled “Actualizar objetos correctamente”function Perfil() {const [usuario, setUsuario] = useState({ nombre: "Ana", email: "ana@mail.com", edad: 25});
// ❌ MAL - Muta el objeto directamenteconst cambiarNombre = (nuevoNombre) => { usuario.nombre = nuevoNombre; // No funciona setUsuario(usuario);};
// ✅ BIEN - Crea un nuevo objeto con spreadconst cambiarNombre = (nuevoNombre) => { setUsuario({ ...usuario, // Copia todas las propiedades nombre: nuevoNombre // Sobrescribe solo nombre });};
// ✅ BIEN - Forma funcionalconst cambiarEmail = (nuevoEmail) => { setUsuario(prev => ({ ...prev, email: nuevoEmail }));};
return ( <div> <p>Nombre: {usuario.nombre}</p> <p>Email: {usuario.email}</p> <input value={usuario.nombre} onChange={(e) => cambiarNombre(e.target.value)} /> </div>);}Objetos anidados
Section titled “Objetos anidados”function Formulario() {const [datos, setDatos] = useState({ personal: { nombre: "", edad: 0 }, direccion: { calle: "", ciudad: "" }});
// Actualizar propiedad anidadaconst cambiarNombre = (nombre) => { setDatos(prev => ({ ...prev, personal: { ...prev.personal, nombre: nombre } }));};
const cambiarCiudad = (ciudad) => { setDatos(prev => ({ ...prev, direccion: { ...prev.direccion, ciudad: ciudad } }));};
return ( <form> <input placeholder="Nombre" value={datos.personal.nombre} onChange={(e) => cambiarNombre(e.target.value)} /> <input placeholder="Ciudad" value={datos.direccion.ciudad} onChange={(e) => cambiarCiudad(e.target.value)} /> </form>);}📋 5.6 Estado con arrays
Section titled “📋 5.6 Estado con arrays”Operaciones comunes con arrays
Section titled “Operaciones comunes con arrays”function ListaTareas() {const [tareas, setTareas] = useState([ { id: 1, texto: "Aprender React", completada: false }, { id: 2, texto: "Hacer ejercicio", completada: true }]);
// AGREGAR elementoconst agregarTarea = (texto) => { const nuevaTarea = { id: Date.now(), texto: texto, completada: false }; setTareas(prev => [...prev, nuevaTarea]);};
// ELIMINAR elementoconst eliminarTarea = (id) => { setTareas(prev => prev.filter(tarea => tarea.id !== id));};
// ACTUALIZAR elementoconst toggleCompletada = (id) => { setTareas(prev => prev.map(tarea => tarea.id === id ? { ...tarea, completada: !tarea.completada } : tarea ));};
// EDITAR texto de elementoconst editarTarea = (id, nuevoTexto) => { setTareas(prev => prev.map(tarea => tarea.id === id ? { ...tarea, texto: nuevoTexto } : tarea ));};
return ( <ul> {tareas.map(tarea => ( <li key={tarea.id}> <span style={{ textDecoration: tarea.completada ? 'line-through' : 'none' }}> {tarea.texto} </span> <button onClick={() => toggleCompletada(tarea.id)}> {tarea.completada ? '↩️' : '✅'} </button> <button onClick={() => eliminarTarea(tarea.id)}>🗑️</button> </li> ))} </ul>);}Resumen de operaciones
Section titled “Resumen de operaciones”| Operación | Método | Ejemplo |
|---|---|---|
| Agregar al final | spread + nuevo | [...arr, nuevo] |
| Agregar al inicio | nuevo + spread | [nuevo, ...arr] |
| Eliminar | filter | arr.filter(x => x.id !== id) |
| Actualizar | map | arr.map(x => x.id === id ? {...x, prop: val} : x) |
| Reemplazar todo | asignar nuevo | setArr(nuevoArray) |
✅ 5.7 Buenas prácticas con estado
Section titled “✅ 5.7 Buenas prácticas con estado”Mantener el estado mínimo
Section titled “Mantener el estado mínimo”// ❌ MAL - Estado redundantefunction Carrito() {const [items, setItems] = useState([]);const [total, setTotal] = useState(0); // Redundanteconst [cantidad, setCantidad] = useState(0); // Redundante
// Hay que mantener sincronizados manualmente}
// ✅ BIEN - Calcular valores derivadosfunction Carrito() {const [items, setItems] = useState([]);
// Valores derivados (se calculan automáticamente)const total = items.reduce((sum, item) => sum + item.precio, 0);const cantidad = items.length;
return ( <div> <p>Items: {cantidad}</p> <p>Total: ${total}</p> </div>);}Agrupar estado relacionado
Section titled “Agrupar estado relacionado”// ❌ Muchos estados separados para datos relacionadosconst [x, setX] = useState(0);const [y, setY] = useState(0);
// ✅ Un objeto para datos relacionadosconst [posicion, setPosicion] = useState({ x: 0, y: 0 });
// ActualizarsetPosicion({ x: 100, y: 200 });Evitar estado duplicado
Section titled “Evitar estado duplicado”// ❌ MAL - Duplicar datos de props en estadofunction Perfil({ usuario }) {const [nombre, setNombre] = useState(usuario.nombre);// Si usuario cambia, nombre no se actualiza}
// ✅ BIEN - Usar props directamentefunction Perfil({ usuario }) {return <h1>{usuario.nombre}</h1>;}
// ✅ Si necesitas editar, usa un estado temporalfunction PerfilEditable({ usuario, onGuardar }) {const [nombreEditado, setNombreEditado] = useState(usuario.nombre);
const guardar = () => { onGuardar(nombreEditado);};
return ( <div> <input value={nombreEditado} onChange={(e) => setNombreEditado(e.target.value)} /> <button onClick={guardar}>Guardar</button> </div>);}📝 Resumen
Section titled “📝 Resumen”
🐝