Hooks en React

Gestión avanzada de estado y efectos en componentes funcionales

Objetivos de Aprendizaje

Introducción a los Hooks

¿Qué son los Hooks?

Los Hooks son funciones especiales introducidas en React 16.8 que permiten a los componentes funcionales:

  • Acceder al estado y ciclo de vida de React
  • Reutilizar lógica de estado entre componentes
  • Reducir la complejidad del código
  • Eliminar la necesidad de componentes de clase

Reglas de los Hooks:

  • Solo llamar Hooks en el nivel superior (no en bucles, condiciones o funciones anidadas)
  • Solo llamar Hooks desde componentes de React o Custom Hooks
  • Los nombres de Hooks personalizados deben comenzar con "use"

Ventajas de los Hooks

  • Simplificación: Eliminan la necesidad de clases y this
  • Reutilización: Facilita compartir lógica entre componentes
  • Organización: Agrupa código relacionado en el mismo lugar
  • Legibilidad: Código más limpio y fácil de entender
  • Testing: Más fácil de probar y aislar lógica

Comparación Clase vs Hooks:

useState

Conceptos Básicos

useState es el Hook fundamental para gestionar estado local en componentes funcionales.

// Sintaxis básica
const [state, setState] = useState(initialValue);

// Ejemplo práctico
const [count, setCount] = useState(0);
  • state: Valor actual del estado
  • setState: Función para actualizar el estado
  • initialValue: Valor inicial del estado

Las actualizaciones de estado con useState no fusionan objetos como this.setState en clases. Para objetos, debes manualmente fusionar el estado anterior.

Actualización del Estado

Existen dos formas de actualizar el estado:

// 1. Valor directo
setCount(5);

// 2. Función que recibe el estado anterior
setCount(prevCount => prevCount + 1);

La segunda forma es recomendada cuando:

  • El nuevo estado depende del anterior
  • Se necesita evitar condiciones de carrera
  • Se trabaja con estados complejos

useEffect

Fundamentos
Dependencias
Casos de Uso

¿Qué es useEffect?

useEffect permite ejecutar efectos secundarios en componentes funcionales:

useEffect(() => {
    // Código del efecto
    return () => {
        // Limpieza (opcional)
    };
}, [dependencies]);

Los efectos secundarios incluyen:

  • Peticiones a APIs (data fetching)
  • Suscripciones a eventos
  • Manipulación manual del DOM
  • Temporizadores

Ciclo de Vida

useEffect combina los métodos de ciclo de vida de clases:

  • componentDidMount: Efecto con array de dependencias vacío
  • componentDidUpdate: Efecto con dependencias específicas
  • componentWillUnmount: Función de limpieza retornada por el efecto
Montaje
useEffect(() => {}, [])
Actualización
useEffect(() => {}, [dep])
Desmontaje
return () => {}

Array de Dependencias

El array de dependencias controla cuándo se ejecuta el efecto:

Dependencias Comportamiento
[] (vacío) Se ejecuta solo al montar el componente
[dep1, dep2] Se ejecuta cuando alguna dependencia cambia
Sin array Se ejecuta después de cada renderizado

Incluir todas las dependencias que se usan dentro del efecto. Omisiones pueden causar bugs.

Ejemplo Práctico

Casos Comunes

  • Fetching de datos:
    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch('api/data');
            const data = await response.json();
            setData(data);
        };
        fetchData();
    }, []);
  • Event listeners:
    useEffect(() => {
        const handleClick = () => console.log('Clicked');
        document.addEventListener('click', handleClick);
        return () => document.removeEventListener('click', handleClick);
    }, []);
  • Temporizadores:
    useEffect(() => {
        const timer = setTimeout(() => {
            setTime(new Date());
        }, 1000);
        return () => clearTimeout(timer);
    }, [time]);

Data Fetching Avanzado

useContext y useReducer

React Context

Context proporciona una forma de compartir valores entre componentes sin pasar props manualmente en cada nivel.

// 1. Crear contexto
const MyContext = React.createContext(defaultValue);

// 2. Proveer contexto
<MyContext.Provider value={someValue}>
    {/* Componentes hijos */}
</MyContext.Provider>

// 3. Consumir contexto
const value = useContext(MyContext);

Casos de uso comunes:

  • Tema de la aplicación (light/dark mode)
  • Autenticación del usuario
  • Idioma/localización
  • Configuración global

useContext

useContext permite acceder al valor de un contexto desde cualquier componente hijo.

useReducer

useReducer es una alternativa a useState para manejar estado complejo:

const [state, dispatch] = useReducer(reducer, initialState);

// Reducer es una función pura
function reducer(state, action) {
    switch (action.type) {
        case 'ACTION_TYPE':
            return { ...state, /* cambios */ };
        default:
            return state;
    }
}

// Disparar acciones
dispatch({ type: 'ACTION_TYPE', payload: data });

Ventajas sobre useState:

  • Mejor para lógica compleja de estado
  • Más fácil de testear
  • Separa la lógica de actualización del componente
  • Útil para estados con múltiples subvalores

Ejemplo: Carrito de Compras

Custom Hooks

¿Qué son Custom Hooks?

Los Custom Hooks son funciones JavaScript que:

  • Comienzan con "use" (convención)
  • Pueden llamar a otros Hooks
  • Permiten extraer lógica de componentes para reutilizarla
  • No tienen JSX (no renderizan UI)
// Ejemplo: useFetch
function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        const fetchData = async () => {
            try {
                const response = await fetch(url);
                const json = await response.json();
                setData(json);
            } catch (err) {
                setError(err);
            } finally {
                setLoading(false);
            }
        };
        
        fetchData();
    }, [url]);
    
    return { data, loading, error };
}

// Uso en componente
function MyComponent() {
    const { data, loading, error } = useFetch('api/data');
    // ...
}

Ejemplo: useLocalStorage

Buenas Prácticas para Custom Hooks

  • Nomenclatura: Siempre comenzar con "use"
  • Unicidad: Cada Hook debe encapsular una sola pieza de lógica
  • Estado mínimo: Mantener el estado interno al mínimo necesario
  • Dependencias: Manejar cuidadosamente las dependencias de useEffect
  • Documentación: Documentar claramente el propósito y API del Hook
  • Testing: Probar independientemente de los componentes

Ejemplos de Custom Hooks útiles:

  • useFetch: Manejo de peticiones HTTP
  • useLocalStorage: Sincronización con localStorage
  • useDebounce: Retrasar ejecución de funciones
  • useMediaQuery: Responder a media queries CSS
  • useOnClickOutside: Detectar clics fuera de un elemento

Comparación de Hooks

useState vs useReducer

useState useReducer
Casos de uso Estado simple, valores independientes Estado complejo, múltiples subvalores relacionados
Lógica de actualización Directa en el componente Centralizada en el reductor
Testing Requiere renderizar componente Reductor testeable independientemente
Rendimiento Mejor para actualizaciones simples Mejor para actualizaciones complejas

useContext vs Redux

useContext Redux
Complejidad Baja, integrado en React Alta, requiere configuración
Casos de uso Estado global que no cambia frecuentemente Estado complejo que cambia frecuentemente
Rendimiento Puede causar re-renders innecesarios Optimizado para evitar re-renders
Herramientas React DevTools Redux DevTools avanzadas

Recomendaciones

  • Comienza con useState y evoluciona a useReducer cuando sea necesario
  • Usa useContext para temas, autenticación o configuración
  • Considera Redux solo para aplicaciones muy grandes y complejas
  • Crea Custom Hooks para lógica reutilizable
  • No abuses de useEffect - considera si la lógica puede ir en eventos

Resumen Semana 7

Conceptos Clave

  • Hooks permiten estado y efectos en componentes funcionales
  • useState para estado local simple
  • useEffect para efectos secundarios y ciclo de vida
  • useContext para acceder a valores de contexto
  • useReducer para estado complejo con lógica centralizada
  • Custom Hooks para reutilizar lógica entre componentes

Hooks Dominados

  • useState - Gestión básica de estado
  • useEffect - Efectos secundarios y sus dependencias
  • useContext - Consumo de contexto
  • useReducer - Manejo de estado complejo
  • Custom Hooks - Lógica reutilizable

Próximos Pasos

  • Profundizar en hooks avanzados: useCallback, useMemo, useRef
  • Aprender patrones de composición de Hooks
  • Explorar librerías de Hooks populares (react-use, ahooks)
  • Practicar integración de múltiples Hooks en proyectos reales
  • Dominar testing de componentes con Hooks

Ejercicios de Laboratorio

Ejercicio 1: Contador Avanzado

Implementé un contador con funcionalidades avanzadas usando Hooks:

  • Contador con incremento/decremento
  • Historial de cambios
  • Persistencia en localStorage
  • Reset del contador
  • Uso de useState, useEffect y Custom Hook

Resultado:

Ejercicio 2 Custom Hook useFetch

Implementé un Custom Hook useFetch para manejar peticiones HTTP:

  • Manejo de estados: loading, error, data
  • Cancelación de peticiones al desmontar
  • Reutilizable en cualquier componente
  • Tipado con TypeScript (opcional)
  • Implementación en componente de ejemplo

Resultado:

Reflexión sobre el Aprendizaje

¿Qué aprendí?

Esta semana profundicé en uno de los conceptos más importantes de React moderno:

  • Hooks fundamentales: Domino el uso de useState para gestión de estado local y useEffect para efectos secundarios.
  • Context API: Aprendí a compartir estado global entre componentes sin prop drilling usando useContext.
  • Estado complejo: Comprendí cuándo y cómo usar useReducer para manejar lógica de estado más sofisticada.
  • Reutilización: Creé mis primeros Custom Hooks para encapsular y reutilizar lógica entre componentes.
  • Buenas prácticas: Entendí las reglas de los Hooks y cómo organizar mi código para mantenerlo limpio y eficiente.

El momento más revelador fue cuando implementé el mismo componente primero con clases y luego con Hooks, viendo cómo estos últimos simplifican enormemente el código.

¿Cómo aprendí?

Mi proceso de aprendizaje fue práctico y basado en la experimentación:

  • Documentación oficial: Estudié a fondo la documentación de React sobre Hooks.
  • Ejercicios progresivos: Comencé con useState simple y avancé hasta combinaciones complejas de múltiples Hooks.
  • Comparación: Implementé los mismos componentes con clases y con Hooks para entender las diferencias.
  • Proyectos reales: Apliqué Hooks en pequeños proyectos para ver su comportamiento en escenarios reales.
  • Debugging: Usé React DevTools para entender el flujo de actualización de componentes con Hooks.

Un ejercicio particularmente útil fue refactorizar un componente de clase existente para usar Hooks, lo que me ayudó a entender las diferencias conceptuales.

Próximos Pasos

Para continuar mi aprendizaje sobre Hooks:

  • Profundizar en hooks avanzados como useMemo, useCallback y useRef.
  • Aprender patrones de composición de Hooks para manejar lógica compleja.
  • Explorar librerías populares de Hooks como react-use.
  • Practicar testing de componentes que usan Hooks.
  • Implementar un proyecto completo usando exclusivamente Hooks para todas las necesidades de estado y efectos.

También planeo investigar cómo optimizar el rendimiento de aplicaciones que usan intensivamente Hooks, especialmente para evitar re-renders innecesarios.