Domina React Hooks - De Básico a Avanzado

intermedio | 60 min de lectura | 2024.12.15

Lo que Aprenderás en Este Tutorial

✓ useState - Fundamentos de gestión de estado
✓ useEffect - Manejo de efectos secundarios
✓ useContext - Compartir estado global
✓ useReducer - Gestión de estado complejo
✓ useMemo/useCallback - Optimización de rendimiento
✓ Custom Hooks - Reutilización de lógica

Prerrequisitos

  • Conocimiento básico de JavaScript
  • Entendimiento básico de React (componentes, props)
  • Node.js instalado

Configuración del Proyecto

# Crear proyecto
npx create-react-app hooks-tutorial
cd hooks-tutorial

# Iniciar servidor de desarrollo
npm start

Paso 1: useState - Fundamentos de Gestión de Estado

Uso Básico

// src/components/Counter.jsx
import { useState } from 'react';

export default function Counter() {
  // [valor del estado, función de actualización] = useState(valor inicial)
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Contador: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Incrementar
      </button>
      <button onClick={() => setCount(count - 1)}>
        Decrementar
      </button>
      <button onClick={() => setCount(0)}>
        Resetear
      </button>
    </div>
  );
}

Gestión de Estado de Objeto

// src/components/UserForm.jsx
import { useState } from 'react';

export default function UserForm() {
  const [user, setUser] = useState({
    nombre: '',
    email: '',
    edad: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    // Usa spread operator para preservar valores existentes
    setUser(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <form>
      <input
        name="nombre"
        value={user.nombre}
        onChange={handleChange}
        placeholder="Nombre"
      />
      <input
        name="email"
        value={user.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="edad"
        type="number"
        value={user.edad}
        onChange={handleChange}
        placeholder="Edad"
      />
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </form>
  );
}

Paso 2: useEffect - Manejo de Efectos Secundarios

useEffect Básico

// src/components/Timer.jsx
import { useState, useEffect } from 'react';

export default function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    let interval = null;

    if (isRunning) {
      interval = setInterval(() => {
        setSeconds(prev => prev + 1);
      }, 1000);
    }

    // Función de limpieza
    return () => {
      if (interval) clearInterval(interval);
    };
  }, [isRunning]); // Se ejecuta cuando isRunning cambia

  return (
    <div>
      <p>Tiempo transcurrido: {seconds} segundos</p>
      <button onClick={() => setIsRunning(!isRunning)}>
        {isRunning ? 'Detener' : 'Iniciar'}
      </button>
      <button onClick={() => setSeconds(0)}>Resetear</button>
    </div>
  );
}

Obtención de Datos

// src/components/UserList.jsx
import { useState, useEffect } from 'react';

export default function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        setLoading(true);
        const response = await fetch(
          'https://jsonplaceholder.typicode.com/users'
        );
        if (!response.ok) throw new Error('Error al obtener datos');
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []); // Array vacío = solo se ejecuta al montar

  if (loading) return <p>Cargando...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name} ({user.email})</li>
      ))}
    </ul>
  );
}

Paso 3: useContext - Compartir Estado Global

Creando y Usando Context

// src/context/ThemeContext.jsx
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme debe usarse dentro de ThemeProvider');
  }
  return context;
}

Paso 4: Custom Hooks - Reutilización de Lógica

useLocalStorage

// src/hooks/useLocalStorage.js
import { useState, useEffect } from 'react';

export function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

useFetch

// src/hooks/useFetch.js
import { useState, useEffect } from 'react';

export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();

    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url, {
          signal: abortController.signal
        });
        if (!response.ok) throw new Error('Error de red');
        const json = await response.json();
        setData(json);
        setError(null);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => abortController.abort();
  }, [url]);

  return { data, loading, error };
}

Mejores Prácticas

1. Consejos de useState
   - Mantén el estado mínimo
   - Deriva valores mediante computación, no useState
   - Usa spread operator al actualizar objetos

2. Consejos de useEffect
   - Configura arrays de dependencias correctamente
   - No olvides funciones de limpieza
   - Cuidado con los bucles infinitos

3. Optimización de Rendimiento
   - Evita optimización excesiva
   - Mide antes de optimizar
   - Usa memo, useMemo, useCallback solo donde sea necesario

4. Custom Hooks
   - Usa para reutilización de lógica
   - Los nombres deben empezar con "use"
   - Sigue el principio de responsabilidad única

Resumen

React Hooks permiten que los componentes funcionales manejen estado y efectos secundarios. Después de dominar los básicos useState y useEffect, avanza a gestión de estado complejo con useContext y useReducer, y optimiza el rendimiento con useMemo y useCallback.

← Volver a la lista