FoundryKit

Getting Started

Quick start guide for using FoundryKit hooks

Getting Started

Installation

Install the hooks package using your preferred package manager:

# Using pnpm (recommended)
pnpm add @foundrykit/hooks

# Using npm
npm install @foundrykit/hooks

# Using yarn
yarn add @foundrykit/hooks

Prerequisites

Before using FoundryKit hooks, ensure you have:

  • React 19+ - For React hooks functionality
  • TypeScript - For type safety (recommended)

Basic Usage

Import and use hooks in your components:

import { useLocalStorage, useDebouncedValue, useMediaQuery } from '@foundrykit/hooks'
import { useState } from 'react'

function MyComponent() {
  // Store data in localStorage
  const [theme, setTheme] = useLocalStorage('theme', 'light')
  
  // Debounce search input
  const [searchQuery, setSearchQuery] = useState('')
  const debouncedQuery = useDebouncedValue(searchQuery, 300)
  
  // Respond to media queries
  const isMobile = useMediaQuery('(max-width: 768px)')

  return (
    <div>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Current theme: {theme}
      </button>
      
      <input
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="Search..."
      />
      
      {isMobile ? (
        <p>Mobile view</p>
      ) : (
        <p>Desktop view</p>
      )}
    </div>
  )
}

Package Structure

The hooks package is organized as follows:

package.json
tsconfig.json
README.md

Hook Categories

State Management

  • useLocalStorage - Persist state in localStorage
  • useDebouncedValue - Debounce values and callbacks

UI Interactions

  • useClickOutside - Detect clicks outside an element
  • useMediaQuery - React to media query changes

Quick Examples

import { useDebouncedValue } from '@foundrykit/hooks'
import { useState, useEffect } from 'react'

function SearchForm() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebouncedValue(query, 500)

  useEffect(() => {
    if (debouncedQuery) {
      // Perform search with debounced query
      performSearch(debouncedQuery)
    }
  }, [debouncedQuery])

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

Theme Toggle with Persistence

import { useLocalStorage } from '@foundrykit/hooks'

function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage('theme', 'light')

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light')
  }

  return (
    <button onClick={toggleTheme}>
      Switch to {theme === 'light' ? 'dark' : 'light'} mode
    </button>
  )
}

Responsive Component

import { useMediaQuery } from '@foundrykit/hooks'

function ResponsiveComponent() {
  const isMobile = useMediaQuery('(max-width: 768px)')
  const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)')
  const isDesktop = useMediaQuery('(min-width: 1025px)')

  return (
    <div>
      {isMobile && <MobileLayout />}
      {isTablet && <TabletLayout />}
      {isDesktop && <DesktopLayout />}
    </div>
  )
}

Click Outside Detection

import { useClickOutside } from '@foundrykit/hooks'
import { useRef, useState } from 'react'

function Dropdown() {
  const [isOpen, setIsOpen] = useState(false)
  const dropdownRef = useRef<HTMLDivElement>(null)

  useClickOutside(dropdownRef, () => {
    setIsOpen(false)
  })

  return (
    <div ref={dropdownRef} className="relative">
      <button onClick={() => setIsOpen(!isOpen)}>
        Toggle Dropdown
      </button>
      
      {isOpen && (
        <div className="absolute top-full left-0 bg-white border rounded shadow">
          <div>Dropdown content</div>
        </div>
      )}
    </div>
  )
}

TypeScript Support

All hooks include full TypeScript support:

import { useLocalStorage, useDebouncedValue } from '@foundrykit/hooks'

// TypeScript automatically infers types
const [user, setUser] = useLocalStorage<User>('user', {
  id: 1,
  name: 'John Doe',
  email: 'john@example.com'
})

const [searchTerm, setSearchTerm] = useState('')
const debouncedSearchTerm = useDebouncedValue<string>(searchTerm, 300)

Custom Hook Patterns

Combining Multiple Hooks

import { useLocalStorage, useDebouncedValue, useMediaQuery } from '@foundrykit/hooks'
import { useState } from 'react'

function useSearchWithPersistence() {
  const [searchQuery, setSearchQuery] = useLocalStorage('searchQuery', '')
  const debouncedQuery = useDebouncedValue(searchQuery, 300)
  const isMobile = useMediaQuery('(max-width: 768px)')

  return {
    searchQuery,
    setSearchQuery,
    debouncedQuery,
    isMobile
  }
}

function SearchComponent() {
  const { searchQuery, setSearchQuery, debouncedQuery, isMobile } = useSearchWithPersistence()

  return (
    <div>
      <input
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder={isMobile ? "Search..." : "Search for anything..."}
      />
      <p>Searching for: {debouncedQuery}</p>
    </div>
  )
}

Hook Composition

import { useLocalStorage, useDebouncedValue } from '@foundrykit/hooks'
import { useState, useEffect } from 'react'

function usePersistentSearch() {
  const [query, setQuery] = useLocalStorage('searchQuery', '')
  const debouncedQuery = useDebouncedValue(query, 500)
  const [results, setResults] = useState([])
  const [isLoading, setIsLoading] = useState(false)

  useEffect(() => {
    if (debouncedQuery) {
      setIsLoading(true)
      performSearch(debouncedQuery)
        .then(setResults)
        .finally(() => setIsLoading(false))
    } else {
      setResults([])
    }
  }, [debouncedQuery])

  return {
    query,
    setQuery,
    debouncedQuery,
    results,
    isLoading
  }
}

Error Handling

Safe Hook Usage

import { useLocalStorage } from '@foundrykit/hooks'
import { useState, useEffect } from 'react'

function SafeLocalStorage() {
  const [hasLocalStorage, setHasLocalStorage] = useState(true)
  const [theme, setTheme] = useLocalStorage('theme', 'light')

  useEffect(() => {
    try {
      // Test localStorage availability
      localStorage.setItem('test', 'test')
      localStorage.removeItem('test')
    } catch (error) {
      setHasLocalStorage(false)
      console.warn('localStorage not available:', error)
    }
  }, [])

  if (!hasLocalStorage) {
    // Fallback to regular state
    const [fallbackTheme, setFallbackTheme] = useState('light')
    return (
      <button onClick={() => setFallbackTheme(fallbackTheme === 'light' ? 'dark' : 'light')}>
        Theme: {fallbackTheme}
      </button>
    )
  }

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Theme: {theme}
    </button>
  )
}

Performance Considerations

Memoization with Hooks

import { useDebouncedValue } from '@foundrykit/hooks'
import { useState, useMemo, useCallback } from 'react'

function OptimizedSearch() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebouncedValue(query, 300)

  // Memoize expensive operations
  const filteredResults = useMemo(() => {
    return performExpensiveFilter(debouncedQuery)
  }, [debouncedQuery])

  // Memoize callbacks
  const handleSearch = useCallback((searchQuery: string) => {
    setQuery(searchQuery)
  }, [])

  return (
    <div>
      <input
        value={query}
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="Search..."
      />
      <div>
        {filteredResults.map(result => (
          <div key={result.id}>{result.name}</div>
        ))}
      </div>
    </div>
  )
}

Next Steps