Skip to main content

Custom Hooks

The core package provides a comprehensive collection of custom React hooks for common use cases.

Storage Hooks

useLocalStorageState

Persist state in localStorage with automatic serialization and deserialization. Location: @workspace/core/hooks/use-local-storage-state
import { useLocalStorageState } from '@workspace/core/hooks/use-local-storage-state'

function UserPreferences() {
  const [theme, setTheme] = useLocalStorageState('theme', {
    defaultValue: 'light',
  })

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current: {theme}
    </button>
  )
}
key
string
required
localStorage key to store the value
options.defaultValue
T
Default value if no stored value exists
options.serializer
(value: T) => string
Custom serialization function (defaults to JSON.stringify)
options.deserializer
(value: string) => T
Custom deserialization function (defaults to JSON.parse)
Returns: [T, (value: T) => void] - State value and setter function

useSessionStorageState

Persist state in sessionStorage with automatic serialization. Location: @workspace/core/hooks/use-session-storage-state
import { useSessionStorageState } from '@workspace/core/hooks/use-session-storage-state'

function FormState() {
  const [formData, setFormData] = useSessionStorageState('form', {
    defaultValue: { name: '', email: '' },
  })

  return (
    <input
      value={formData.name}
      onChange={(e) => setFormData({ ...formData, name: e.target.value })}
    />
  )
}
Parameters: Same as useLocalStorageState Returns: [T, (value: T) => void] - State value and setter function

Media & Browser Hooks

useMediaQuery

React to CSS media query changes with automatic updates on resize. Location: @workspace/core/hooks/use-media-query
import { useMediaQuery } from '@workspace/core/hooks/use-media-query'

function ResponsiveComponent() {
  const isMobile = useMediaQuery('(max-width: 768px)')
  const isDark = useMediaQuery('(prefers-color-scheme: dark)')
  const isPrint = useMediaQuery('print')

  return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>
}
query
string
required
CSS media query string to match against
Returns: boolean - Whether the media query matches

useCopyToClipboard

Copy text to clipboard with status feedback. Location: @workspace/core/hooks/use-copy-to-clipboard
import { useCopyToClipboard } from '@workspace/core/hooks/use-copy-to-clipboard'

function CopyButton({ text }: { text: string }) {
  const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })

  return (
    <button onClick={() => copyToClipboard(text)}>
      {isCopied ? 'Copied!' : 'Copy'}
    </button>
  )
}
options.timeout
number
default:"1000"
Duration in ms before isCopied resets to false
Returns:
  • isCopied: boolean - Whether text was recently copied
  • copyToClipboard: (value: string) => void - Function to copy text

useColorMode

Manage color theme with localStorage persistence and system preference detection. Location: @workspace/core/hooks/use-color-mode
import { useColorMode } from '@workspace/core/hooks/use-color-mode'

function ThemeToggle() {
  const [colorMode, setColorMode] = useColorMode({
    selector: 'html',
    attribute: 'class',
    initialValue: 'auto',
    storageKey: 'app-color-scheme',
  })

  return (
    <button onClick={() => setColorMode(colorMode === 'dark' ? 'light' : 'dark')}>
      {colorMode === 'dark' ? '🌙' : '☀️'}
    </button>
  )
}
options.selector
string
default:"'html'"
CSS selector for the target element
options.attribute
string
default:"'class'"
HTML attribute to modify (‘class’ or custom attribute)
options.initialValue
'light' | 'dark' | 'auto'
default:"'auto'"
Initial color mode
options.storageKey
string
default:"'app-color-scheme'"
localStorage key for persistence
options.disableTransition
boolean
default:"true"
Disable CSS transitions during mode switch
Returns: [colorMode, setColorMode] - Current mode and setter

Timer Hooks

useInterval

Declarative interval with automatic cleanup and pause/resume. Location: @workspace/core/hooks/use-interval
import { useInterval } from '@workspace/core/hooks/use-interval'

function Counter() {
  const [count, setCount] = useState(0)
  const [delay, setDelay] = useState(1000)

  // Clear function can be called to manually stop
  const clear = useInterval(
    () => setCount(count + 1),
    delay,
    { immediate: true }
  )

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setDelay(null)}>Pause</button>
      <button onClick={() => setDelay(1000)}>Resume</button>
      <button onClick={clear}>Stop</button>
    </div>
  )
}
fn
() => void
required
Callback function to execute at each interval
delay
number | null | undefined
Interval duration in milliseconds. Set to null/undefined to pause.
options.immediate
boolean
default:"false"
Execute callback immediately on mount/delay change
Returns: () => void - Function to manually clear the interval

useTimeout

Declarative timeout with automatic cleanup. Location: @workspace/core/hooks/use-timeout
import { useTimeout } from '@workspace/core/hooks/use-timeout'

function DelayedMessage() {
  const [visible, setVisible] = useState(false)

  const clear = useTimeout(() => {
    setVisible(true)
  }, 3000)

  return visible ? <p>Message appeared!</p> : null
}
fn
() => void
required
Callback function to execute after timeout
delay
number | null | undefined
Timeout duration in milliseconds. Set to null/undefined to cancel.
Returns: () => void - Function to manually clear the timeout

useRafInterval

RequestAnimationFrame-based interval for smooth animations. Location: @workspace/core/hooks/use-raf-interval Returns: () => void - Function to clear the interval

useRafTimeout

RequestAnimationFrame-based timeout for smooth animations. Location: @workspace/core/hooks/use-raf-timeout Returns: () => void - Function to clear the timeout

State Management Hooks

useControllableValue

Manage both controlled and uncontrolled component states. Location: @workspace/core/hooks/use-controllable-value
import { useControllableValue } from '@workspace/core/hooks/use-controllable-value'

function CustomInput(props) {
  const [value, setValue] = useControllableValue(props, {
    defaultValue: '',
  })

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

// Controlled usage
<CustomInput value={value} onChange={setValue} />

// Uncontrolled usage
<CustomInput defaultValue="initial" />
props
object
required
Component props potentially containing value/onChange
options.defaultValue
T
Default value for uncontrolled mode
options.valuePropName
string
default:"'value'"
Name of the value prop
options.trigger
string
default:"'onChange'"
Name of the change handler prop
Returns: [T, (value: T) => void] - Current value and setter

useResetState

State with a reset function to return to initial value. Location: @workspace/core/hooks/use-reset-state
import { useResetState } from '@workspace/core/hooks/use-reset-state'

function Form() {
  const [formData, setFormData, resetFormData] = useResetState({
    name: '',
    email: '',
  })

  return (
    <form onReset={resetFormData}>
      <input
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
      />
      <button type="reset">Reset</button>
    </form>
  )
}
Returns: [state, setState, resetState]

useDynamicList

Manage dynamic lists with unique keys for each item. Location: @workspace/core/hooks/use-dynamic-list
import { useDynamicList } from '@workspace/core/hooks/use-dynamic-list'

function TodoList() {
  const { list, remove, getKey, insert, push } = useDynamicList(['Task 1'])

  return (
    <div>
      {list.map((item, index) => (
        <div key={getKey(index)}>
          <span>{item}</span>
          <button onClick={() => remove(index)}>Delete</button>
        </div>
      ))}
      <button onClick={() => push('New Task')}>Add</button>
    </div>
  )
}
Returns:
  • list: T[] - Current list
  • insert: (index: number, item: T) => void - Insert at position
  • merge: (index: number, items: T[]) => void - Merge multiple items
  • replace: (index: number, item: T) => void - Replace item
  • remove: (index: number) => void - Remove item
  • getKey: (index: number) => number - Get unique key
  • getIndex: (key: number) => number - Get index from key
  • move: (oldIndex: number, newIndex: number) => void - Move item
  • push: (item: T) => void - Add to end
  • pop: () => void - Remove from end
  • unshift: (item: T) => void - Add to start
  • shift: () => void - Remove from start
  • resetList: (newList: T[]) => void - Reset entire list

useHistoryTravel

Time-travel state management with undo/redo. Location: @workspace/core/hooks/use-history-travel

Lifecycle Hooks

useMount

Execute a function once when component mounts. Location: @workspace/core/hooks/use-mount
import { useMount } from '@workspace/core/hooks/use-mount'

function Analytics() {
  useMount(() => {
    console.log('Component mounted')
    trackPageView()
  })

  return <div>Content</div>
}
fn
() => void
required
Function to execute on mount

useUpdateEffect

Run effect on updates only, not on mount. Location: @workspace/core/hooks/use-update-effect
import { useUpdateEffect } from '@workspace/core/hooks/use-update-effect'

function SearchResults({ query }) {
  useUpdateEffect(() => {
    // Only runs when query changes, not on initial mount
    fetchResults(query)
  }, [query])

  return <div>Results...</div>
}

useUpdateLayoutEffect

Run layout effect on updates only. Location: @workspace/core/hooks/use-update-layout-effect

useUpdate

Force component re-render. Location: @workspace/core/hooks/use-update
import { useUpdate } from '@workspace/core/hooks/use-update'

function Component() {
  const update = useUpdate()

  return (
    <button onClick={update}>
      Force Re-render
    </button>
  )
}
Returns: () => void - Function to trigger re-render

Utility Hooks

useLatest

Get the latest value without closure issues. Location: @workspace/core/hooks/use-latest
import { useLatest } from '@workspace/core/hooks/use-latest'

function Component({ onChange }) {
  const latestOnChange = useLatest(onChange)

  useEffect(() => {
    const timer = setInterval(() => {
      // Always calls the latest onChange
      latestOnChange.current()
    }, 1000)
    return () => clearInterval(timer)
  }, []) // No dependency on onChange needed
}
value
T
required
Value to keep up-to-date
Returns: React.MutableRefObject<T> - Ref containing latest value

useMemoizedFn

Memoize function with stable reference. Location: @workspace/core/hooks/use-memoized-fn
import { useMemoizedFn } from '@workspace/core/hooks/use-memoized-fn'

function Form() {
  const [data, setData] = useState({})

  // Function reference never changes
  const handleSubmit = useMemoizedFn(() => {
    console.log('Submitting:', data)
  })

  return <button onClick={handleSubmit}>Submit</button>
}
fn
Function
required
Function to memoize
Returns: Memoized function with stable reference

useMultipleRefs

Combine multiple refs into one. Location: @workspace/core/hooks/use-multiple-refs

useSelections

Manage multi-select state. Location: @workspace/core/hooks/use-selections

useAutoScroll

Auto-scroll to bottom when content changes. Location: @workspace/core/hooks/use-auto-scroll

Hook Factories

createUseStorageState

Factory for creating custom storage hooks. Location: @workspace/core/hooks/create-use-storage-state
import { createUseStorageState } from '@workspace/core/hooks/create-use-storage-state'

// Create custom storage hook
const useCustomStorage = createUseStorageState(() => customStorage)

createUpdateEffect

Factory for creating custom update effect hooks. Location: @workspace/core/hooks/create-update-effect

Build docs developers (and LLMs) love