🔧 Snippets
React Hooks

React Hooks

A collection of custom React hooks for common use cases.

useLocalStorage

Store and retrieve values from localStorage with React state synchronization.

import { useState, useEffect } from 'react'
 
export function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === 'undefined') {
      return initialValue
    }
    try {
      const item = window.localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch (error) {
      console.error(error)
      return initialValue
    }
  })
 
  const setValue = (value: T) => {
    try {
      setStoredValue(value)
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(value))
      }
    } catch (error) {
      console.error(error)
    }
  }
 
  return [storedValue, setValue]
}
 
// Usage
function Component() {
  const [name, setName] = useLocalStorage('name', 'Guest')
  
  return (
    <input
      value={name}
      onChange={(e) => setName(e.target.value)}
    />
  )
}

useMediaQuery

Detect media query matches.

import { useState, useEffect } from 'react'
 
export function useMediaQuery(query: string): boolean {
  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
}
 
// Usage
function ResponsiveComponent() {
  const isMobile = useMediaQuery('(max-width: 768px)')
  
  return (
    <div>
      {isMobile ? 'Mobile View' : 'Desktop View'}
    </div>
  )
}

useOnClickOutside

Detect clicks outside of an element.

import { useEffect, RefObject } from 'react'
 
export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
  ref: RefObject<T>,
  handler: (event: MouseEvent | TouchEvent) => void
) {
  useEffect(() => {
    const listener = (event: MouseEvent | TouchEvent) => {
      const el = ref?.current
      if (!el || el.contains(event.target as Node)) {
        return
      }
      handler(event)
    }
 
    document.addEventListener('mousedown', listener)
    document.addEventListener('touchstart', listener)
 
    return () => {
      document.removeEventListener('mousedown', listener)
      document.removeEventListener('touchstart', listener)
    }
  }, [ref, handler])
}
 
// Usage
function Modal() {
  const modalRef = useRef<HTMLDivElement>(null)
  const [isOpen, setIsOpen] = useState(false)
 
  useOnClickOutside(modalRef, () => setIsOpen(false))
 
  return isOpen ? (
    <div ref={modalRef}>
      Modal Content
    </div>
  ) : null
}

useFetch

Simple data fetching hook with loading and error states.

import { useState, useEffect } from 'react'
 
interface FetchState<T> {
  data: T | null
  loading: boolean
  error: Error | null
}
 
export function useFetch<T>(url: string): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    loading: true,
    error: null,
  })
 
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url)
        if (!response.ok) throw new Error('Network response was not ok')
        const data = await response.json()
        setState({ data, loading: false, error: null })
      } catch (error) {
        setState({ data: null, loading: false, error: error as Error })
      }
    }
 
    fetchData()
  }, [url])
 
  return state
}
 
// Usage
function UserProfile({ userId }: { userId: string }) {
  const { data, loading, error } = useFetch<User>(`/api/users/${userId}`)
 
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  if (!data) return null
 
  return <div>{data.name}</div>
}

useToggle

Toggle boolean state easily.

import { useState, useCallback } from 'react'
 
export function useToggle(initialValue: boolean = false): [boolean, () => void] {
  const [value, setValue] = useState(initialValue)
  const toggle = useCallback(() => setValue(v => !v), [])
  return [value, toggle]
}
 
// Usage
function Component() {
  const [isVisible, toggleVisible] = useToggle(false)
 
  return (
    <div>
      <button onClick={toggleVisible}>Toggle</button>
      {isVisible && <div>Content</div>}
    </div>
  )
}

usePrevious

Get the previous value of a state or prop.

import { useEffect, useRef } from 'react'
 
export function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>()
 
  useEffect(() => {
    ref.current = value
  }, [value])
 
  return ref.current
}
 
// Usage
function Counter() {
  const [count, setCount] = useState(0)
  const prevCount = usePrevious(count)
 
  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  )
}

useWindowSize

Track window dimensions.

import { useState, useEffect } from 'react'
 
interface WindowSize {
  width: number
  height: number
}
 
export function useWindowSize(): WindowSize {
  const [windowSize, setWindowSize] = useState<WindowSize>({
    width: 0,
    height: 0,
  })
 
  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      })
    }
 
    handleResize()
    window.addEventListener('resize', handleResize)
    
    return () => window.removeEventListener('resize', handleResize)
  }, [])
 
  return windowSize
}
 
// Usage
function Component() {
  const { width, height } = useWindowSize()
 
  return (
    <div>
      Window size: {width} x {height}
    </div>
  )
}

useCopyToClipboard

Copy text to clipboard.

import { useState } from 'react'
 
export function useCopyToClipboard(): [string | null, (text: string) => Promise<void>] {
  const [copiedText, setCopiedText] = useState<string | null>(null)
 
  const copy = async (text: string) => {
    if (!navigator?.clipboard) {
      console.warn('Clipboard not supported')
      return
    }
 
    try {
      await navigator.clipboard.writeText(text)
      setCopiedText(text)
    } catch (error) {
      console.error('Copy failed:', error)
      setCopiedText(null)
    }
  }
 
  return [copiedText, copy]
}
 
// Usage
function CopyButton({ text }: { text: string }) {
  const [copiedText, copy] = useCopyToClipboard()
 
  return (
    <button onClick={() => copy(text)}>
      {copiedText ? 'Copied!' : 'Copy'}
    </button>
  )
}

All hooks are TypeScript-ready and production-tested. Feel free to use them in your projects!