TechLead

Mas Hooks

useContext, useRef, useMemo, useCallback y hooks personalizados

Hooks Adicionales

Ademas de useState y useEffect, React proporciona varios hooks adicionales para casos de uso especificos.

useContext

import { createContext, useContext, useState } from 'react';

// Crear contexto
const TemaContext = createContext();

// Proveedor
function ProveedorTema({ children }) {
  const [tema, setTema] = useState('claro');
  return (
    <TemaContext.Provider value={{ tema, setTema }}>
      {children}
    </TemaContext.Provider>
  );
}

// Consumir con useContext
function BotonTema() {
  const { tema, setTema } = useContext(TemaContext);
  return (
    <button onClick={() => setTema(tema === 'claro' ? 'oscuro' : 'claro')}>
      Tema actual: {tema}
    </button>
  );
}

useRef

import { useRef, useEffect } from 'react';

// Referencia a elemento DOM
function InputConFoco() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

// Valor persistente sin re-renderizado
function Temporizador() {
  const contadorRef = useRef(0);
  const intervaloRef = useRef(null);

  const iniciar = () => {
    intervaloRef.current = setInterval(() => {
      contadorRef.current += 1;
      console.log(contadorRef.current);
    }, 1000);
  };

  const detener = () => {
    clearInterval(intervaloRef.current);
  };

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

useMemo

import { useMemo, useState } from 'react';

function ListaFiltrada({ items }) {
  const [filtro, setFiltro] = useState('');
  const [ordenAsc, setOrdenAsc] = useState(true);

  // Memoriza el resultado del calculo costoso
  const itemsFiltradosYOrdenados = useMemo(() => {
    console.log('Calculando lista filtrada...');
    return items
      .filter(item => item.nombre.includes(filtro))
      .sort((a, b) => ordenAsc ? a.nombre.localeCompare(b.nombre) : b.nombre.localeCompare(a.nombre));
  }, [items, filtro, ordenAsc]); // Solo recalcula si estas dependencias cambian

  return (
    <div>
      <input value={filtro} onChange={e => setFiltro(e.target.value)} />
      <ul>
        {itemsFiltradosYOrdenados.map(item => (
          <li key={item.id}>{item.nombre}</li>
        ))}
      </ul>
    </div>
  );
}

useCallback

import { useCallback, useState, memo } from 'react';

// Componente hijo memorizado
const Boton = memo(({ onClick, children }) => {
  console.log('Boton renderizado:', children);
  return <button onClick={onClick}>{children}</button>;
});

function Padre() {
  const [cuenta, setCuenta] = useState(0);
  const [texto, setTexto] = useState('');

  // Sin useCallback, se crea nueva funcion en cada render
  // Con useCallback, la funcion se memoriza
  const incrementar = useCallback(() => {
    setCuenta(c => c + 1);
  }, []);

  return (
    <div>
      <p>Cuenta: {cuenta}</p>
      <input value={texto} onChange={e => setTexto(e.target.value)} />
      <Boton onClick={incrementar}>Incrementar</Boton>
    </div>
  );
}

Hooks Personalizados

// Hook personalizado para localStorage
function useLocalStorage(clave, valorInicial) {
  const [valor, setValor] = useState(() => {
    const guardado = localStorage.getItem(clave);
    return guardado ? JSON.parse(guardado) : valorInicial;
  });

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

  return [valor, setValor];
}

// Uso
function App() {
  const [nombre, setNombre] = useLocalStorage('nombre', '');
  return (
    <input value={nombre} onChange={e => setNombre(e.target.value)} />
  );
}

Reglas de los Hooks

  • Solo en el nivel superior: No en loops, condiciones o funciones anidadas
  • Solo en componentes React: O en hooks personalizados
  • Prefijo "use": Los hooks personalizados deben empezar con "use"

Hooks de React 18 y 19

Las versiones recientes de React introducen nuevos hooks poderosos:

useId — IDs Únicos para Accesibilidad

Genera IDs estables únicos para atributos de accesibilidad, seguros para SSR.

import { useId } from "react";

function FormField({ label }) {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </div>
  );
}

useTransition — Actualizaciones No Bloqueantes

Marca actualizaciones de estado como no urgentes para mantener la UI responsiva.

import { useState, useTransition } from "react";

function SearchableList({ items }) {
  const [query, setQuery] = useState("");
  const [filtered, setFiltered] = useState(items);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    setQuery(e.target.value); // Urgente: actualizar input
    startTransition(() => {
      // No urgente: filtrar lista grande
      setFiltered(items.filter(i => i.name.includes(e.target.value)));
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <span>Actualizando...</span>}
      {filtered.map(item => <div key={item.id}>{item.name}</div>)}
    </div>
  );
}

useDeferredValue — Valor con Renderizado Diferido

Difiere la actualización de un valor para mantener la UI responsiva durante renderizados pesados.

import { useState, useDeferredValue } from "react";

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  // Usa deferredQuery para renderizado pesado
  const results = heavySearch(deferredQuery);
  return results.map(r => <div key={r.id}>{r.name}</div>);
}

Para más hooks modernos como useFormStatus, useOptimistic y use(), consulta el tema de React 19.