Created
January 19, 2026 20:34
-
-
Save wolftrax5/4b1e1b7a85840de036d20d499d43c736 to your computer and use it in GitHub Desktop.
Hooks used on React
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useEffect, RefObject } from 'react' | |
| /** | |
| * Hook que detecta clicks fuera de uno o múltiples elementos | |
| * | |
| * @param ref - Ref del elemento o array de refs de elementos a observar | |
| * @param handler - Callback que se ejecuta cuando se hace click fuera del elemento | |
| */ | |
| export function useClickOutside( | |
| ref: RefObject<HTMLElement> | RefObject<HTMLElement>[], | |
| handler: (event: MouseEvent | TouchEvent) => void | |
| ): void { | |
| useEffect(() => { | |
| const handleClickOutside = (event: MouseEvent | TouchEvent) => { | |
| // Convertir ref a array si es un solo ref | |
| const refs = Array.isArray(ref) ? ref : [ref] | |
| // Verificar si el click fue dentro de alguno de los elementos | |
| const isClickInside = refs.some(refItem => { | |
| // Verificar que el ref tenga un elemento actual | |
| if (!refItem.current) { | |
| return false | |
| } | |
| // Verificar si el target del evento está dentro del elemento | |
| return refItem.current.contains(event.target as Node) | |
| }) | |
| // Si el click fue fuera de todos los elementos, ejecutar el handler | |
| if (!isClickInside) { | |
| handler(event) | |
| } | |
| } | |
| // Agregar event listeners para mouse y touch events | |
| document.addEventListener('mousedown', handleClickOutside) | |
| document.addEventListener('touchstart', handleClickOutside) | |
| // Cleanup: remover event listeners cuando el componente se desmonte | |
| // o cuando cambien las dependencias | |
| return () => { | |
| document.removeEventListener('mousedown', handleClickOutside) | |
| document.removeEventListener('touchstart', handleClickOutside) | |
| } | |
| }, [ref, handler]) // Re-ejecutar si ref o handler cambian | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useState, useEffect } from 'react' | |
| /** | |
| * Hook que retorna un valor debounced después de un delay especificado | |
| * | |
| * @template T - Tipo genérico del valor a debounce | |
| * @param value - El valor que se quiere debounce | |
| * @param delay - El delay en milisegundos antes de actualizar el valor debounced | |
| * @returns El valor debounced | |
| */ | |
| export function useDebounce<T>(value: T, delay: number): T { | |
| const [debouncedValue, setDebouncedValue] = useState<T>(value) | |
| useEffect(() => { | |
| // Crear un timeout que actualizará el valor después del delay | |
| const handler = setTimeout(() => { | |
| setDebouncedValue(value) | |
| }, delay) | |
| // Cleanup: limpiar el timeout si el valor o delay cambian antes de que se complete | |
| // o si el componente se desmonta | |
| return () => { | |
| clearTimeout(handler) | |
| } | |
| }, [value, delay]) // Re-ejecutar cuando value o delay cambien | |
| return debouncedValue | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Hook para hacer peticiones HTTP | |
| * | |
| * Este hook: | |
| * - Hace peticiones HTTP | |
| * - Es genérico y tipado | |
| * - Maneja estados: loading, data, error | |
| * - Soporta refetch manual | |
| * - Cancela peticiones (AbortController) | |
| * - Cache opcional | |
| * - Retry automático con backoff exponencial | |
| */ | |
| import { useState, useEffect, useRef, useCallback } from 'react' | |
| interface UseFetchOptions { | |
| enabled?: boolean | |
| cache?: boolean | |
| retry?: number | |
| retryDelay?: number | |
| } | |
| interface UseFetchReturn<T> { | |
| data: T | null | |
| loading: boolean | |
| error: Error | null | |
| refetch: () => void | |
| } | |
| const cache = new Map<string, { data: unknown; timestamp: number }>() | |
| const CACHE_DURATION = 5 * 60 * 1000 // 5 minutos | |
| export function useFetch<T>( | |
| url: string | null, | |
| options?: UseFetchOptions | |
| ): UseFetchReturn<T> { | |
| const { | |
| enabled = true, | |
| cache: useCache = false, | |
| retry = 0, | |
| retryDelay = 1000 | |
| } = options || {} | |
| const [data, setData] = useState<T | null>(null) | |
| const [loading, setLoading] = useState<boolean>(false) | |
| const [error, setError] = useState<Error | null>(null) | |
| const abortControllerRef = useRef<AbortController | null>(null) | |
| const retryCountRef = useRef<number>(0) | |
| const fetchData = useCallback(async () => { | |
| if (!url || !enabled) { | |
| return | |
| } | |
| // Verificar cache | |
| if (useCache && cache.has(url)) { | |
| const cached = cache.get(url)! | |
| if (Date.now() - cached.timestamp < CACHE_DURATION) { | |
| setData(cached.data as T) | |
| setLoading(false) | |
| setError(null) | |
| return | |
| } | |
| } | |
| // Cancelar petición anterior si existe | |
| if (abortControllerRef.current) { | |
| abortControllerRef.current.abort() | |
| } | |
| // Crear nuevo AbortController | |
| abortControllerRef.current = new AbortController() | |
| const signal = abortControllerRef.current.signal | |
| setLoading(true) | |
| setError(null) | |
| const attemptFetch = async (attempt: number): Promise<void> => { | |
| try { | |
| const response = await fetch(url, { signal }) | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`) | |
| } | |
| const jsonData = await response.json() | |
| // Guardar en cache si está habilitado | |
| if (useCache) { | |
| cache.set(url, { data: jsonData, timestamp: Date.now() }) | |
| } | |
| setData(jsonData as T) | |
| setLoading(false) | |
| setError(null) | |
| retryCountRef.current = 0 | |
| } catch (err: unknown) { | |
| // No hacer retry si fue cancelado | |
| if (err instanceof Error && err.name === 'AbortError') { | |
| return | |
| } | |
| // Intentar retry si quedan intentos | |
| if (attempt < retry) { | |
| const delay = retryDelay * Math.pow(2, attempt) // Backoff exponencial | |
| await new Promise(resolve => setTimeout(resolve, delay)) | |
| return attemptFetch(attempt + 1) | |
| } | |
| setError(err instanceof Error ? err : new Error(String(err))) | |
| setLoading(false) | |
| retryCountRef.current = 0 | |
| } | |
| } | |
| await attemptFetch(0) | |
| }, [url, enabled, useCache, retry, retryDelay]) | |
| useEffect(() => { | |
| fetchData() | |
| return () => { | |
| if (abortControllerRef.current) { | |
| abortControllerRef.current.abort() | |
| } | |
| } | |
| }, [fetchData]) | |
| const refetch = useCallback(() => { | |
| retryCountRef.current = 0 | |
| fetchData() | |
| }, [fetchData]) | |
| return { | |
| data, | |
| loading, | |
| error, | |
| refetch | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Hook que observa la intersección de elementos | |
| * | |
| * Este hook: | |
| * - Observa intersección de elementos | |
| * - Configuración de threshold y rootMargin | |
| * - Retorna si el elemento está visible | |
| * - Cleanup automático del observer | |
| */ | |
| import { useState, useEffect, RefObject } from 'react' | |
| interface UseIntersectionObserverOptions { | |
| threshold?: number | number[] | |
| rootMargin?: string | |
| root?: Element | null | |
| } | |
| export function useIntersectionObserver( | |
| elementRef: RefObject<Element>, | |
| options?: UseIntersectionObserverOptions | |
| ): boolean { | |
| const [isIntersecting, setIsIntersecting] = useState<boolean>(false) | |
| useEffect(() => { | |
| const element = elementRef.current | |
| if (!element) { | |
| return | |
| } | |
| const observer = new IntersectionObserver( | |
| ([entry]) => { | |
| setIsIntersecting(entry.isIntersecting) | |
| }, | |
| { | |
| threshold: options?.threshold ?? 0, | |
| rootMargin: options?.rootMargin ?? '0px', | |
| root: options?.root ?? null | |
| } | |
| ) | |
| observer.observe(element) | |
| return () => { | |
| observer.disconnect() | |
| } | |
| }, [elementRef, options?.threshold, options?.rootMargin, options?.root]) | |
| return isIntersecting | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Hook que sincroniza estado con localStorage | |
| * | |
| * Este hook: | |
| * - Sincroniza estado con localStorage | |
| * - Es genérico y tipado | |
| * - Maneja serialización/deserialización automática | |
| * - Maneja errores de localStorage | |
| * - Sincroniza entre múltiples tabs/windows | |
| * - Retorna [value, setValue, removeValue] | |
| */ | |
| import { useState, useEffect, useCallback } from 'react' | |
| export function useLocalStorage<T>( | |
| key: string, | |
| initialValue: T | |
| ): [T, (value: T | ((val: T) => T)) => void, () => void] { | |
| // Función para leer del localStorage | |
| const readValue = useCallback((): T => { | |
| if (typeof window === 'undefined') { | |
| return initialValue | |
| } | |
| try { | |
| const item = window.localStorage.getItem(key) | |
| return item ? JSON.parse(item) : initialValue | |
| } catch (error) { | |
| console.warn(`Error reading localStorage key "${key}":`, error) | |
| return initialValue | |
| } | |
| }, [initialValue, key]) | |
| // Estado que se sincroniza con localStorage | |
| const [storedValue, setStoredValue] = useState<T>(readValue) | |
| // Función para actualizar el valor | |
| const setValue = useCallback( | |
| (value: T | ((val: T) => T)) => { | |
| try { | |
| // Permitir que value sea una función para actualizar basado en el valor anterior | |
| const valueToStore = value instanceof Function ? value(storedValue) : value | |
| // Guardar estado | |
| setStoredValue(valueToStore) | |
| // Guardar en localStorage | |
| if (typeof window !== 'undefined') { | |
| window.localStorage.setItem(key, JSON.stringify(valueToStore)) | |
| // Disparar evento personalizado para sincronizar entre tabs | |
| window.dispatchEvent(new Event('local-storage')) | |
| } | |
| } catch (error) { | |
| console.warn(`Error setting localStorage key "${key}":`, error) | |
| } | |
| }, | |
| [key, storedValue] | |
| ) | |
| // Función para remover el valor | |
| const removeValue = useCallback(() => { | |
| try { | |
| setStoredValue(initialValue) | |
| if (typeof window !== 'undefined') { | |
| window.localStorage.removeItem(key) | |
| window.dispatchEvent(new Event('local-storage')) | |
| } | |
| } catch (error) { | |
| console.warn(`Error removing localStorage key "${key}":`, error) | |
| } | |
| }, [key, initialValue]) | |
| // Efecto para sincronizar entre tabs/windows | |
| useEffect(() => { | |
| setStoredValue(readValue()) | |
| const handleStorageChange = () => { | |
| setStoredValue(readValue()) | |
| } | |
| // Escuchar cambios en localStorage de otras tabs/windows | |
| window.addEventListener('storage', handleStorageChange) | |
| // Escuchar eventos personalizados del mismo tab | |
| window.addEventListener('local-storage', handleStorageChange) | |
| return () => { | |
| window.removeEventListener('storage', handleStorageChange) | |
| window.removeEventListener('local-storage', handleStorageChange) | |
| } | |
| }, [readValue]) | |
| return [storedValue, setValue, removeValue] | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Hook que obtiene el valor anterior de un estado | |
| * | |
| * Este hook: | |
| * - Obtiene el valor anterior de un estado | |
| * - Es genérico y tipado | |
| * - Retorna undefined en el primer render | |
| */ | |
| import { useRef, useEffect } from 'react' | |
| export function usePrevious<T>(value: T): T | undefined { | |
| const ref = useRef<T>() | |
| useEffect(() => { | |
| ref.current = value | |
| }, [value]) | |
| return ref.current | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Hook que alterna entre true/false | |
| * | |
| * Este hook: | |
| * - Alterna entre true/false | |
| * - Retorna [value, toggle, setValue] | |
| */ | |
| import { useState, useCallback } from 'react' | |
| export function useToggle( | |
| initialValue: boolean = false | |
| ): [boolean, () => void, (value: boolean) => void] { | |
| const [value, setValue] = useState<boolean>(initialValue) | |
| const toggle = useCallback(() => { | |
| setValue(prev => !prev) | |
| }, []) | |
| const setValueDirect = useCallback((newValue: boolean) => { | |
| setValue(newValue) | |
| }, []) | |
| return [value, toggle, setValueDirect] | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment