Este guía está diseñada para llevarte desde los conceptos básicos hasta patrones avanzados, con ejemplos prácticos, consejos de buenas prácticas y advertencias sobre errores comunes.
📋 Tabla de contenidos
-
useState
– El fundamento del estado -
useEffect
– Efectos secundarios y ciclo de vida -
useContext
– Compartir estado sin props -
useReducer
– Lógica de Estado Compleja y predecible -
useRef
– Referencias, Almacenamiento y Escape -
useMemo
yuseCallback
– Optimizando el Rendimiento - Custom Hooks – Creando Lógica Reutilizable
- Errores Comunes y Soluciones
🔹 1. useState
– Manejar el estado local
¿Qué es y para qué sirve?
useState
es el hook más fundamental. Te permite añadir una "variable de estado" a tus componentes de función. A diferencia de una variable normal, cuando el valor de esta variable cambia, React automáticamente vuelve a renderizar el componente para reflejar el cambio en la interfaz.
Úsalo para: manejar datos que cambian con el tiempo y que afectan a lo que se ve en pantalla, como el valor de un input, si un modal está abierto o los datos de un contador.
Sintaxis y parámetros
const [estado, setEstado] = useState(valorInicial);
-
valorInicial
: El valor que tendráestado
la primera vez que se renderice el componente. Puede ser cualquier tipo de dato (número, string, objeto, array, etc.). -
estado
: La variable que contiene el valor actual del estado. Es de solo lectura. -
setEstado
: La función que usas para actualizar el valor deestado
. Al llamarla, se dispara una nueva renderización.
Ejemplo práctico detallado: formulario de registro
En lugar de un simple contador, veamos un ejemplo más realista: un formulario de registro que maneja un objeto como estado.
import React, { useState } from 'react';
function FormularioRegistro() {
const [usuario, setUsuario] = useState({
nombre: '',
email: '',
password: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
// Usamos la forma funcional de setState para asegurar que tenemos el estado más reciente
setUsuario(prevUsuario => ({
...prevUsuario,
[name]: value,
}));
};
const handleSubmit = (e) => {
e.preventDefault();
// Aquí normalmente enviarías los datos a un servidor
alert(`Usuario registrado: ${usuario.nombre} con email ${usuario.email}`);
console.log(usuario);
};
return (
<form onSubmit={handleSubmit}>
<h3>Formulario de Registro</h3>
<input
type="text"
name="nombre"
placeholder="Nombre"
value={usuario.nombre}
onChange={handleChange}
/>
<input
type="email"
name="email"
placeholder="Email"
value={usuario.email}
onChange={handleChange}
/>
<input
type="password"
name="password"
placeholder="Contraseña"
value={usuario.password}
onChange={handleChange}
/>
<button type="submit">Registrarse</button>
</form>
);
}
🚀 Ver el código interactivo en CodeSandbox Haz clic para abrir el sandbox y experimentar con el código
💡 Tip: Puedes modificar el código en el editor de la izquierda y ver los cambios en tiempo real.
✅ Buenas practicas y patrones comunes
- Agrupar estado relacionado: Si varios estados cambian juntos, considera agruparlos en un objeto o array. Esto es útil en formularios.
- Usar la actualización funcional: Cuando el nuevo estado depende del anterior (como en un contador), usa
setContador(c => c + 1)
. Esto evita problemas con el estado "viejo" (stale state) en operaciones asíncronas. - La actualización de estado es asíncrona: No esperes que el valor del estado se actualice inmediatamente después de llamar a
setEstado
. Si necesitas realizar una acción después de que el estado cambie, usauseEffect
. - No mutes el estado directamente: Especialmente con objetos y arrays. En lugar de
usuario.nombre = 'nuevo'
, crea un nuevo objeto:setUsuario({...usuario, nombre: 'nuevo'})
. La inmutabilidad es clave en React.
🚨 Errores comunes y cómo evitarlos
-
Error: Modificar el estado directamente.
// MAL ❌ const [user, setUser] = useState({ name: 'Ana' }); user.name = 'Juan'; // No dispara re-render y es un anti-patrón setUser(user);
- Solución: Siempre crea un nuevo objeto/array.
// BIEN ✅ setUser({ ...user, name: 'Juan' });
-
Error: Llamar a
useState
dentro de condicionales, bucles o funciones anidadas.- Solución: Los hooks siempre deben llamarse en el nivel superior del componente. React depende del orden en que se llaman para funcionar correctamente.
🚀 Retos prácticos
- Contador con Límite: Crea un contador que no pueda pasar de 10 ni ser menor que 0.
💻 Código base (haz clic para mostrar)
import React, { useState } from "react";
// Reto: Contador con Límite
// Crea un contador que no pueda pasar de 10 ni ser menor que 0.
// Debe tener botones para incrementar, decrementar y resetear.
export default function ContadorLimiteReto(): React.ReactElement {
// TODO: Implementar useState para el contador
// const [contador, setContador] = useState<number>(0);
// TODO: Implementar función incrementar
// - Solo debe incrementar si el contador es menor que 10
const incrementar = () => {
// Tu código aquí
};
// TODO: Implementar función decrementar
// - Solo debe decrementar si el contador es mayor que 0
const decrementar = () => {
// Tu código aquí
};
// TODO: Implementar función resetear
// - Debe poner el contador en 0
const resetear = () => {
// Tu código aquí
};
return (
<div style={{ padding: "20px", textAlign: "center" }}>
<h1>Contador con Límite</h1>
<div style={{ fontSize: "3rem", margin: "20px 0" }}>
{/* TODO: Mostrar el valor del contador */}
0
</div>
<div>
<button
onClick={decrementar}
style={{ margin: "0 5px", padding: "10px 20px" }}
// TODO: Deshabilitar si el contador es 0
>
-
</button>
<button
onClick={resetear}
style={{ margin: "0 5px", padding: "10px 20px" }}
>
Reset
</button>
<button
onClick={incrementar}
style={{ margin: "0 5px", padding: "10px 20px" }}
// TODO: Deshabilitar si el contador es 10
>
+
</button>
</div>
<p style={{ marginTop: "20px", color: "#666" }}>
Límites: 0 - 10
</p>
</div>
);
}
- Input con Validación: Haz un input que muestre un mensaje de error si el texto tiene menos de 5 caracteres.
💻 Código base (haz clic para mostrar)
import React, { useState } from "react";
// Reto: Input con Validación
// Haz un input que muestre un mensaje de error si el texto tiene menos de 5 caracteres.
export default function InputValidacionReto(): React.ReactElement {
// TODO: Implementar useState para el texto del input
// TODO: Crear una función que determine si el texto es válido
return (
<div style={{ padding: "20px", maxWidth: "400px" }}>
<h1>Input con Validación</h1>
<div style={{ marginBottom: "20px" }}>
<label style={{ display: "block", marginBottom: "5px" }}>
Escribe al menos 5 caracteres:
</label>
<input
type="text"
// TODO: Conectar value y onChange con el estado
// value={_____}
// onChange={_____}
placeholder="Escribe aquí..."
style={{
width: "100%",
padding: "10px",
fontSize: "16px",
border: "2px solid #ccc", // TODO: Cambiar color según validación
borderRadius: "4px"
}}
/>
</div>
{/* TODO: Mostrar mensaje de error si el texto no es válido */}
{/* TODO: Mostrar mensaje de éxito si el texto es válido */}
<div style={{ marginTop: "20px", fontSize: "14px", color: "#666" }}>
{/* TODO: Mostrar contador de caracteres */}
Caracteres: 0/5
</div>
</div>
);
}
- Toggle de Visibilidad: Un botón que muestre u oculte un texto.
💻 Código base (haz clic para mostrar)
import React, { useState } from "react";
// Reto: Toggle de Visibilidad
// Un botón que muestre u oculte un texto.
export default function ToggleVisibilidadReto(): React.ReactElement {
// TODO: Implementar useState para controlar la visibilidad
// TODO: Implementar función para alternar la visibilidad
const alternarVisibilidad = () => {
// Tu código aquí
};
return (
<div style={{ padding: "20px" }}>
<h1>Toggle de Visibilidad</h1>
<button
onClick={alternarVisibilidad}
style={{
padding: "10px 20px",
fontSize: "16px",
marginBottom: "20px"
}}
>
{/* TODO: Cambiar el texto del botón según el estado */}
Mostrar Texto
</button>
{/* TODO: Mostrar u ocultar el texto según el estado */}
{/* {_____ && (
<div style={{
padding: "20px",
backgroundColor: "#f0f0f0",
borderRadius: "8px",
border: "2px solid #ddd"
}}>
<h2>¡Texto Visible! 🎉</h2>
<p>
Este texto se muestra y oculta usando useState.
Es un ejemplo perfecto de cómo controlar la renderización
condicional en React.
</p>
</div>
)} */}
<div style={{ marginTop: "20px", fontSize: "14px", color: "#666" }}>
{/* TODO: Mostrar el estado actual */}
Estado actual: Oculto
</div>
</div>
);
}
- Lista de Tareas Simple: Un input para añadir tareas a una lista. Cada tarea debe tener un botón para eliminarla (pista: necesitarás manejar un array en el estado).
💻 Código base (haz clic para mostrar)
import React, { useState } from "react";
// Reto: Lista de Tareas Simple
// Un input para añadir tareas a una lista. Cada tarea debe tener un botón para eliminarla.
// Pista: necesitarás manejar un array en el estado.
interface Tarea {
id: number;
texto: string;
}
export default function ListaTareasReto(): React.ReactElement {
// TODO: Implementar useState para la lista de tareas
// TODO: Implementar useState para el input de nueva tarea
// TODO: Implementar función para agregar tarea
const agregarTarea = () => {
// Verificar que el input no esté vacío
// Crear nueva tarea con id único (puedes usar Date.now())
// Agregar a la lista de tareas
// Limpiar el input
};
// TODO: Implementar función para eliminar tarea
const eliminarTarea = (id: number) => {
// Filtrar la tarea con el id especificado
};
// TODO: Implementar función para manejar Enter en el input
const manejarEnter = (e: React.KeyboardEvent) => {
// Si la tecla es Enter, agregar la tarea
};
return (
<div style={{ padding: "20px", maxWidth: "500px" }}>
<h1>Lista de Tareas</h1>
<div style={{ marginBottom: "20px", display: "flex", gap: "10px" }}>
<input
type="text"
// TODO: Conectar value y onChange
placeholder="Escribe una nueva tarea..."
style={{
flex: 1,
padding: "10px",
fontSize: "16px",
border: "2px solid #ddd",
borderRadius: "4px"
}}
/>
<button
onClick={agregarTarea}
style={{
padding: "10px 20px",
fontSize: "16px",
backgroundColor: "#4CAF50",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer"
}}
>
Agregar
</button>
</div>
{/* TODO: Renderizar la lista de tareas */}
<div style={{ marginTop: "20px", fontSize: "14px", color: "#666" }}>
{/* TODO: Mostrar contador de tareas */}
Total de tareas: 0
</div>
</div>
);
}
Top comments (0)