# Hooks
Semestre 01, 2026



## Definición


Antes de 2019, la lógica con estado solo existía en componentes de clase

* this.state, this.setState, componentDidMount, componentDidUpdate...
* La lógica se repetía en distintos componentes sin forma de reutilizarla
* Los ciclos de vida mezclaban código no relacionado


Los Hooks son funciones especiales que permiten usar estado y otras funciones de React en componentes funcionales

* Introducidos en React 16.8 (2019)
* Permiten extraer y reutilizar lógica entre componentes


React + Hooks = componentes más simples y poderosos.


### Reglas

Dos reglas que siempre deben cumplirse:


1. Solo llamar Hooks en el nivel superior, nunca dentro de `if`, `for` o funciones anidadas


2. Solo llamar Hooks en componentes React, no en funciones JavaScript ordinarias


```jsx[]
// MAL
function Componente() {
  if (condicion) {
    const [valor, setValor] = useState(0); // Rompe la regla 1
  }
}

// BIEN
function Componente() {
  const [valor, setValor] = useState(0);

  if (condicion) {
    // usar valor aquí
  }
}
```


![hooks](/courses/2026/cc3062/assets/hooks.png)



## useState


Permite agregar una variable de estado a un componente funcional

```jsx
import { useState } from 'react';

function Contador() {
  const [conteo, setConteo] = useState(0);

  return (
    <div>
      <p>Conteo: {conteo}</p>
      <button onClick={() => setConteo(conteo + 1)}>
        Incrementar
      </button>
    </div>
  );
}
```


* `useState(valorInicial)` retorna `[estado, setter]`
* Llamar al setter provoca un re-render del componente


### Estado con objetos

El estado puede ser cualquier tipo de dato

```jsx
function Formulario() {
  const [usuario, setUsuario] = useState({
    nombre: '',
    email: '',
  });

  return (
    <input
      value={usuario.nombre}
      onChange={(e) =>
        setUsuario({ ...usuario, nombre: e.target.value })
      }
    />
  );
}
```


* Al actualizar objetos, siempre crear una copia con spread (`...`)
* React compara referencias cambiar el objeto directamente no dispara el re-render


### Estado con arrays

```jsx
function ListaTareas() {
  const [tareas, setTareas] = useState([]);

  function agregarTarea(nueva) {
    setTareas([...tareas, nueva]); // nueva referencia
  }

  function eliminarTarea(id) {
    setTareas(tareas.filter(t => t.id !== id)); // nuevo array
  }

  return (/* ... */);
}
```


| Operación | Método correcto |
|---|---|
| Agregar | `[...array, nuevo]` |
| Eliminar | `array.filter(...)` |
| Modificar | `array.map(...)` |


### Actualización basada en el estado anterior

```jsx
function Contador() {
  const [conteo, setConteo] = useState(0);

  function incrementarDoble() {
    // MAL — puede usar valor desactualizado
    setConteo(conteo + 1);
    setConteo(conteo + 1);

    // BIEN — función updater garantiza el valor más reciente
    setConteo(prev => prev + 1);
    setConteo(prev => prev + 1);
  }
}
```

Usar la función updater `prev =>` cuando el nuevo estado depende del anterior



## useEffect


Efecto es cualquier acción que ocurre fuera del render puro:

* Llamadas a APIs / fetch de datos
* Suscripciones a eventos
* Modificar el título de la página
* Timers (setTimeout, setInterval)
* Manipulación directa del DOM


### Sintaxis de useEffect

```jsx
import { useEffect } from 'react';

useEffect(() => {
  // código a ejecutar (el efecto)
  return () => {
    // función de limpieza (opcional)
  };
}, [/* dependencias */]);
```

* Se ejecuta después de que React pinta la pantalla
* El array de dependencias controla cuándo se vuelve a ejecutar
* La función de retorno limpia recursos cuando el componente se desmonta


### Variantes según dependencias

```jsx
// 1. Sin array — ejecuta después de CADA render
useEffect(() => {
  console.log('Renderizado');
});

// 2. Array vacío — ejecuta solo al MONTAR el componente
useEffect(() => {
  console.log('Montado');
}, []);

// 3. Con dependencias — ejecuta cuando cambie `id`
useEffect(() => {
  console.log('El id cambió:', id);
}, [id]);
```


### fetch de datos

```jsx
function PerfilUsuario({ userId }) {
  const [usuario, setUsuario] = useState(null);

  useEffect(() => {
    fetch(`/api/usuarios/${userId}`)
      .then(res => res.json())
      .then(data => setUsuario(data));
  }, [userId]); // se vuelve a ejecutar si userId cambia

  if (!usuario) return <p>Cargando...</p>;

  return <h1>Hola, {usuario.nombre}</h1>;
}
```


### Título del documento

```jsx
function Tarea({ titulo }) {
  useEffect(() => {
    document.title = `${titulo}`;

    // Limpieza: restaurar el título al desmontar
    return () => {
      document.title = 'Mi App';
    };
  }, [titulo]);

  return <h1>{titulo}</h1>;
}
```

La función de limpieza se llama:
* Antes de ejecutar el efecto nuevamente (cuando cambian las dependencias)
* Cuando el componente se desmonta del DOM


### Suscripción a eventos

```jsx
function VentanaAncho() {
  const [ancho, setAncho] = useState(window.innerWidth);

  useEffect(() => {
    function handleResize() {
      setAncho(window.innerWidth);
    }

    window.addEventListener('resize', handleResize);

    // Limpieza: remover el listener al desmontar
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // solo montar y desmontar

  return <p>Ancho: {ancho}px</p>;
}
```


### Dependencias faltantes

```jsx
function Busqueda({ query }) {
  const [resultados, setResultados] = useState([]);

  // MAL — query se usa pero no está en el array
  useEffect(() => {
    buscar(query).then(setResultados);
  }, []);

  // BIEN — query en las dependencias
  useEffect(() => {
    buscar(query).then(setResultados);
  }, [query]);
}
```

Regla: todo valor del componente que se use dentro del efecto debe estar en el array de dependencias



## useRef


Un ref es una caja mutable cuyo contenido no provoca re-renders al cambiar

```jsx
import { useRef } from 'react';

function Cronometro() {
  const intervaloRef = useRef(null);

  function iniciar() {
    intervaloRef.current = setInterval(() => {
      console.log('tick');
    }, 1000);
  }

  function detener() {
    clearInterval(intervaloRef.current);
  }

  return (
    <>
      <button onClick={iniciar}>Iniciar</button>
      <button onClick={detener}>Detener</button>
    </>
  );
}
```


* `useRef(valorInicial)` devuelve un objeto `{ current: valorInicial }`
* Modificar `.current` no re-renderiza el componente


### useRef vs useState

| | useState | useRef |
|---|---|---|
| ¿Re-renderiza al cambiar? | Sí | No |
| ¿Para mostrar en la UI? | Sí | No |
| Caso de uso | Datos que la UI debe reflejar | Valores internos, timers, IDs |


### Acceder al DOM

El uso más común — obtener una referencia directa a un elemento del DOM

```jsx
function CampoAutoFocus() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus(); // acceso directo al DOM
  }, []);

  return <input ref={inputRef} placeholder="Escribe aquí..." />;
}
```

React asigna el nodo del DOM a `ref.current` después del primer render


### Valor anterior

```jsx
function Componente({ valor }) {
  const valorAnterior = useRef(valor);

  useEffect(() => {
    valorAnterior.current = valor;
  });

  return (
    <p>
      Actual: {valor}, Anterior: {valorAnterior.current}
    </p>
  );
}
```



## useContext


### Prop drilling

```
App (tiene: usuario)
└── Layout
    └── Sidebar
        └── MenuUsuario  ← necesita: usuario
```

Para llegar de App a MenuUsuario hay que pasar usuario por cada nivel intermedio — aunque Layout y Sidebar no lo usen.

Problemático cuando el árbol es profundo.


### useContext: leer el contexto

Context permite "transmitir" datos a cualquier nivel del árbol sin props intermedias

```jsx
import { createContext, useContext } from 'react';

// 1. Crear el contexto
const TemaContext = createContext('claro');

// 2. Proveer el valor en un ancestro
function App() {
  return (
    <TemaContext.Provider value="oscuro">
      <Pagina />
    </TemaContext.Provider>
  );
}

// 3. Consumir en cualquier descendiente
function Boton() {
  const tema = useContext(TemaContext);
  return <button className={`btn-${tema}`}>Clic</button>;
}
```


### Usuario autenticado

```jsx
const UsuarioContext = createContext(null);

function App() {
  const [usuario, setUsuario] = useState(null);

  return (
    <UsuarioContext.Provider value={usuario}>
      <NavBar />
      <Contenido />
    </UsuarioContext.Provider>
  );
}

function NavBar() {
  const usuario = useContext(UsuarioContext);
  return <p>{usuario ? `Hola, ${usuario.nombre}` : 'Invitado'}</p>;
}
```



## useReducer


### Cuando useState se vuelve complejo

```jsx
// Estado con muchas piezas relacionadas
function Formulario() {
  const [nombre, setNombre] = useState('');
  const [email, setEmail] = useState('');
  const [cargando, setCargando] = useState(false);
  const [error, setError] = useState(null);
  const [enviado, setEnviado] = useState(false);

  // lógica dispersa en múltiples setters...
}
```

Cuando el estado tiene múltiples sub-valores que cambian juntos → usar useReducer


### Sintaxis

```jsx
import { useReducer } from 'react';

function reducer(estado, accion) {
  switch (accion.type) {
    case 'INCREMENTAR':
      return { ...estado, conteo: estado.conteo + 1 };
    case 'DECREMENTAR':
      return { ...estado, conteo: estado.conteo - 1 };
    case 'RESET':
      return { conteo: 0 };
    default:
      return estado;
  }
}

function Contador() {
  const [estado, dispatch] = useReducer(reducer, { conteo: 0 });

  return (
    <>
      <p>{estado.conteo}</p>
      <button onClick={() => dispatch({ type: 'INCREMENTAR' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENTAR' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </>
  );
}
```


### Formulario de registro

```jsx
const initialState = { nombre: '', email: '', cargando: false, error: null };

function reducer(state, action) {
  switch (action.type) {
    case 'CAMPO':
      return { ...state, [action.field]: action.value };
    case 'ENVIAR':
      return { ...state, cargando: true, error: null };
    case 'EXITO':
      return { ...state, cargando: false };
    case 'ERROR':
      return { ...state, cargando: false, error: action.mensaje };
    default:
      return state;
  }
}

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

  return (
    <input
      value={state.nombre}
      onChange={e => dispatch({ type: 'CAMPO', field: 'nombre', value: e.target.value })}
    />
  );
}
```



## useMemo y useCallback


En cada render, React re-ejecuta toda la función del componente

```jsx
function ListaProductos({ productos, filtro }) {
  // Se recalcula en cada render aunque productos y filtro no cambien
  const productosFiltrados = productos.filter(p =>
    p.nombre.toLowerCase().includes(filtro.toLowerCase())
  );

  return productosFiltrados.map(p => <Producto key={p.id} {...p} />);
}
```

Si el cálculo es costoso y las dependencias no cambiaron, es un trabajo innecesario.


```jsx
import { useMemo } from 'react';

function ListaProductos({ productos, filtro }) {
  const productosFiltrados = useMemo(() => {
    return productos.filter(p =>
      p.nombre.toLowerCase().includes(filtro.toLowerCase())
    );
  }, [productos, filtro]); // solo recalcula si cambian estos valores

  return productosFiltrados.map(p => <Producto key={p.id} {...p} />);
}
```


* `useMemo(fn, deps)` ejecuta `fn` y guarda el resultado
* Solo lo recalcula cuando alguna dependencia cambia
* Útil para cálculos costosos sobre listas grandes o datos complejos


### useCallback

```jsx
import { useCallback } from 'react';

function Padre() {
  const [conteo, setConteo] = useState(0);

  // Sin useCallback: nueva referencia en cada render → Hijo re-renderiza
  // Con useCallback: misma referencia si las deps no cambian
  const handleClick = useCallback(() => {
    console.log('clic desde hijo');
  }, []); // sin dependencias → misma función siempre

  return (
    <>
      <p>{conteo}</p>
      <button onClick={() => setConteo(c => c + 1)}>+</button>
      <HijoOptimizado onClick={handleClick} />
    </>
  );
}
```


### useMemo vs useCallback

| Hook | Memoriza | Caso de uso |
|---|---|---|
| `useMemo` | El resultado de una función | Cálculos costosos, filtros, ordenamientos |
| `useCallback` | La función en sí | Callbacks pasados a componentes hijos |


```jsx
// useMemo — memoriza el valor
const totalVentas = useMemo(() =>
  ventas.reduce((acc, v) => acc + v.monto, 0),
  [ventas]
);

// useCallback — memoriza la función
const handleSubmit = useCallback((e) => {
  e.preventDefault();
  enviarFormulario(datos);
}, [datos]);
```


### No memoizar

La memoización tiene un costo.

* Cálculos simples: `useMemo(() => a + b, [a, b])` es peor que solo `a + b`
* Todo: añade complejidad sin beneficio



## Custom Hooks


Un Custom Hook es una función JavaScript que empieza con `use` y llama a otros Hooks

```jsx
// Hook personalizado
function useDocumentTitle(titulo) {
  useEffect(() => {
    document.title = titulo;
    return () => { document.title = 'Mi App'; };
  }, [titulo]);
}

// Uso en cualquier componente
function Perfil({ nombre }) {
  useDocumentTitle(`Perfil de ${nombre}`);
  return <h1>{nombre}</h1>;
}

function Configuracion() {
  useDocumentTitle('Configuración');
  return <h1>Ajustes</h1>;
}
```


### useFetch

```jsx
function useFetch(url) {
  const [data, setData] = useState(null);
  const [cargando, setCargando] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setCargando(true);
    fetch(url)
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setCargando(false));
  }, [url]);

  return { data, cargando, error };
}

// Uso
function Usuarios() {
  const { data, cargando, error } = useFetch('/api/usuarios');

  if (cargando) return <p>Cargando...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return <ul>{data.map(u => <li key={u.id}>{u.nombre}</li>)}</ul>;
}
```


### Ventajas

* Reutilización
* Separación de responsabilidades
* Testabilidad
* Legibilidad
