C
Cassa UI

@cassa/hooks

Five utility hooks for common React patterns. All typed, all SSR-safe.

Import

import {
  useDebounce,
  useLocalStorage,
  useMediaQuery,
  useClickOutside,
  usePrevious,
} from '@cassa/hooks'

All hooks

PropTypeDefaultDescription
useDebouncehookDelays updating a value until the user stops changing it.
useLocalStoragehookuseState that persists to localStorage and syncs across tabs.
useMediaQueryhookReactively checks a CSS media query.
useClickOutsidehookFires a callback when the user clicks outside a ref element.
usePrevioushookReturns the value from the previous render.

useDebounce

Returns a debounced copy of the value that only updates after the delay has elapsed. Ideal for search inputs to avoid firing a request on every keystroke.

// signature
useDebounce<T>(value: T, delay: number): T
import { useDebounce } from '@cassa/hooks'

const SearchBar = () => {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 300)

  useEffect(() => {
    if (debouncedQuery) fetchResults(debouncedQuery)
  }, [debouncedQuery]) // fires only after 300ms of no input

  return <input value={query} onChange={e => setQuery(e.target.value)} />
}

useLocalStorage

Drop-in replacement for useState that persists the value to localStorage and syncs across tabs. Safe on SSR — no-ops in Node.

// signature
useLocalStorage<T>(key: string, initialValue: T): [T, setter]
import { useLocalStorage } from '@cassa/hooks'

const ThemeToggle = () => {
  const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light')

  return (
    <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
      {theme === 'light' ? '☾ Dark' : '☀ Light'}
    </button>
  )
}

useMediaQuery

Returns true if the given CSS media query currently matches, and updates reactively when the viewport changes. Returns false on SSR.

// signature
useMediaQuery(query: string): boolean
import { useMediaQuery } from '@cassa/hooks'

const Layout = () => {
  const isMobile = useMediaQuery('(max-width: 768px)')
  const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')

  return isMobile ? <MobileLayout /> : <DesktopLayout />
}

useClickOutside

Returns a ref. Calls callback whenever the user clicks outside the element the ref is attached to. Useful for closing dropdowns and modals.

// signature
useClickOutside<T extends HTMLElement>(callback: () => void): RefObject<T>
import { useClickOutside } from '@cassa/hooks'

const Dropdown = ({ onClose }) => {
  const ref = useClickOutside<HTMLDivElement>(onClose)

  return (
    <div ref={ref} className="absolute top-full bg-white shadow-lg">
      Dropdown content
    </div>
  )
}

usePrevious

Returns the previous render's value. Useful for detecting changes, animating transitions, or comparing old vs new state.

// signature
usePrevious<T>(value: T): T | undefined
import { usePrevious } from '@cassa/hooks'

const Counter = () => {
  const [count, setCount] = useState(0)
  const prevCount = usePrevious(count)

  return (
    <div>
      <p>Now: {count} | Before: {prevCount ?? '—'}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  )
}