React: Creando Custom Hooks Reutilizables

25 de diciembre de 2025
Osman Jimenez
React Hooks JavaScript

Custom Hooks en React

Los custom hooks permiten reutilizar lógica entre componentes. Aprende a crear hooks útiles y reutilizables.

useLocalStorage

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

// Uso
const [name, setName] = useLocalStorage('name', 'John');

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 (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// Uso
const { data, loading, error } = useFetch('/api/users');

useDebounce

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

// Uso
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);

useEffect(() => {
  if (debouncedSearch) {
    searchAPI(debouncedSearch);
  }
}, [debouncedSearch]);

useMediaQuery

function useMediaQuery(query) {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const media = window.matchMedia(query);
    if (media.matches !== matches) {
      setMatches(media.matches);
    }

    const listener = () => setMatches(media.matches);
    media.addEventListener('change', listener);
    return () => media.removeEventListener('change', listener);
  }, [matches, query]);

  return matches;
}

// Uso
const isMobile = useMediaQuery('(max-width: 768px)');

useOnClickOutside

function useOnClickOutside(ref, handler) {
  useEffect(() => {
    const listener = (event) => {
      if (!ref.current || ref.current.contains(event.target)) {
        return;
      }
      handler(event);
    };

    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);

    return () => {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
  }, [ref, handler]);
}

// Uso
const ref = useRef();
useOnClickOutside(ref, () => setIsOpen(false));

useIntersectionObserver

function useIntersectionObserver(ref, options) {
  const [isIntersecting, setIntersecting] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      setIntersecting(entry.isIntersecting);
    }, options);

    if (ref.current) {
      observer.observe(ref.current);
    }

    return () => {
      observer.disconnect();
    };
  }, [ref, options]);

  return isIntersecting;
}

// Uso - Lazy loading
const ref = useRef();
const isVisible = useIntersectionObserver(ref, { threshold: 0.5 });

usePrevious

function usePrevious(value) {
  const ref = useRef();
  
  useEffect(() => {
    ref.current = value;
  }, [value]);
  
  return ref.current;
}

// Uso
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);

useToggle

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  
  const toggle = useCallback(() => {
    setValue(v => !v);
  }, []);
  
  return [value, toggle];
}

// Uso
const [isOpen, toggleOpen] = useToggle();

Mejores Prácticas

  1. Prefijo use: Siempre nombra hooks con 'use'
  2. Reglas de Hooks: Solo llama hooks en el top level
  3. Dependencies: Incluye todas las dependencias en useEffect
  4. Cleanup: Retorna función de limpieza cuando sea necesario
  5. Memoización: Usa useCallback y useMemo apropiadamente

Conclusión

Los custom hooks son una forma poderosa de compartir lógica entre componentes. Crea una librería de hooks reutilizables para tu proyecto y aumenta tu productividad.