Advanced Patterns
Advanced patterns and techniques for using FoundryKit components
Advanced Patterns
This guide covers advanced patterns and techniques for using FoundryKit components in complex scenarios.
State Management Patterns
Centralized Search State
Manage search state across multiple components:
import { createContext, useContext, useState, useCallback } from 'react'
import { SearchBar, Calendar, Pagination } from '@foundrykit/components'
import { Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
interface SearchState {
query: string
date: Date | undefined
page: number
filters: Record<string, any>
}
interface SearchContextType {
searchState: SearchState
updateQuery: (query: string) => void
updateDate: (date: Date | undefined) => void
updatePage: (page: number) => void
updateFilters: (filters: Record<string, any>) => void
resetSearch: () => void
}
const SearchContext = createContext<SearchContextType | undefined>(undefined)
function SearchProvider({ children }: { children: React.ReactNode }) {
const [searchState, setSearchState] = useState<SearchState>({
query: '',
date: undefined,
page: 1,
filters: {}
})
const updateQuery = useCallback((query: string) => {
setSearchState(prev => ({ ...prev, query, page: 1 }))
}, [])
const updateDate = useCallback((date: Date | undefined) => {
setSearchState(prev => ({ ...prev, date, page: 1 }))
}, [])
const updatePage = useCallback((page: number) => {
setSearchState(prev => ({ ...prev, page }))
}, [])
const updateFilters = useCallback((filters: Record<string, any>) => {
setSearchState(prev => ({ ...prev, filters, page: 1 }))
}, [])
const resetSearch = useCallback(() => {
setSearchState({
query: '',
date: undefined,
page: 1,
filters: {}
})
}, [])
return (
<SearchContext.Provider value={{
searchState,
updateQuery,
updateDate,
updatePage,
updateFilters,
resetSearch
}}>
{children}
</SearchContext.Provider>
)
}
function useSearch() {
const context = useContext(SearchContext)
if (!context) {
throw new Error('useSearch must be used within SearchProvider')
}
return context
}
function AdvancedSearchInterface() {
const { searchState, updateQuery, updateDate, updatePage, resetSearch } = useSearch()
return (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Advanced Search</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<SearchBar
placeholder="Search..."
onSearch={updateQuery}
/>
<Calendar
mode="single"
selected={searchState.date}
onSelect={updateDate}
className="rounded-md border"
/>
<div className="flex space-x-2">
<Button onClick={resetSearch}>Reset</Button>
</div>
</CardContent>
</Card>
<Pagination
currentPage={searchState.page}
totalPages={10}
onPageChange={updatePage}
/>
</div>
)
}
Optimistic Updates
Implement optimistic updates for better user experience:
import { useState, useCallback } from 'react'
import { SearchBar, Calendar } from '@foundrykit/components'
import { Button, Card, CardContent } from '@foundrykit/primitives'
function OptimisticSearch() {
const [searchState, setSearchState] = useState({
query: '',
date: undefined as Date | undefined
})
const [optimisticState, setOptimisticState] = useState(searchState)
const [isUpdating, setIsUpdating] = useState(false)
const handleSearch = useCallback(async (query: string) => {
// Optimistically update UI
setOptimisticState(prev => ({ ...prev, query }))
try {
setIsUpdating(true)
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000))
// Update actual state
setSearchState(prev => ({ ...prev, query }))
} catch (error) {
// Revert optimistic update on error
setOptimisticState(searchState)
console.error('Search failed:', error)
} finally {
setIsUpdating(false)
}
}, [searchState])
const handleDateSelect = useCallback(async (date: Date | undefined) => {
setOptimisticState(prev => ({ ...prev, date }))
try {
setIsUpdating(true)
await new Promise(resolve => setTimeout(resolve, 500))
setSearchState(prev => ({ ...prev, date }))
} catch (error) {
setOptimisticState(searchState)
console.error('Date selection failed:', error)
} finally {
setIsUpdating(false)
}
}, [searchState])
return (
<Card>
<CardContent className="space-y-4">
<SearchBar
placeholder="Search..."
onSearch={handleSearch}
disabled={isUpdating}
/>
<Calendar
mode="single"
selected={optimisticState.date}
onSelect={handleDateSelect}
disabled={isUpdating}
/>
{isUpdating && (
<div className="text-sm text-muted-foreground">
Updating...
</div>
)}
</CardContent>
</Card>
)
}
Performance Patterns
Virtual Scrolling with Search
Implement virtual scrolling for large datasets with search:
import { useState, useMemo, useCallback } from 'react'
import { SearchBar, Pagination } from '@foundrykit/components'
import { Card, CardContent } from '@foundrykit/primitives'
interface VirtualListProps {
items: any[]
itemHeight: number
containerHeight: number
renderItem: (item: any, index: number) => React.ReactNode
}
function VirtualList({ items, itemHeight, containerHeight, renderItem }: VirtualListProps) {
const [scrollTop, setScrollTop] = useState(0)
const visibleItems = useMemo(() => {
const startIndex = Math.floor(scrollTop / itemHeight)
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
)
return items.slice(startIndex, endIndex).map((item, index) => ({
item,
index: startIndex + index
}))
}, [items, scrollTop, itemHeight, containerHeight])
const totalHeight = items.length * itemHeight
const offsetY = Math.floor(scrollTop / itemHeight) * itemHeight
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map(({ item, index }) => (
<div key={index} style={{ height: itemHeight }}>
{renderItem(item, index)}
</div>
))}
</div>
</div>
</div>
)
}
function VirtualSearchList({ items }: { items: any[] }) {
const [searchQuery, setSearchQuery] = useState('')
const [currentPage, setCurrentPage] = useState(1)
const itemsPerPage = 100
const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(searchQuery.toLowerCase())
)
}, [items, searchQuery])
const paginatedItems = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage
return filteredItems.slice(startIndex, startIndex + itemsPerPage)
}, [filteredItems, currentPage])
const totalPages = Math.ceil(filteredItems.length / itemsPerPage)
const renderItem = useCallback((item: any, index: number) => (
<div className="p-2 border-b hover:bg-gray-50">
{item.name}
</div>
), [])
return (
<div className="space-y-4">
<SearchBar
placeholder="Search items..."
onSearch={setSearchQuery}
/>
<VirtualList
items={paginatedItems}
itemHeight={40}
containerHeight={400}
renderItem={renderItem}
/>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</div>
)
}
Debounced Search with Caching
Implement debounced search with result caching:
import { useState, useCallback, useMemo } from 'react'
import { SearchBar } from '@foundrykit/components'
import { Card, CardContent } from '@foundrykit/primitives'
interface SearchCache {
[query: string]: {
results: any[]
timestamp: number
}
}
function CachedSearch({ items }: { items: any[] }) {
const [searchQuery, setSearchQuery] = useState('')
const [searchResults, setSearchResults] = useState<any[]>([])
const [isSearching, setIsSearching] = useState(false)
const [cache, setCache] = useState<SearchCache>({})
const CACHE_DURATION = 5 * 60 * 1000 // 5 minutes
const performSearch = useCallback(async (query: string) => {
if (!query.trim()) {
setSearchResults([])
return
}
// Check cache first
const cached = cache[query]
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
setSearchResults(cached.results)
return
}
setIsSearching(true)
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 300))
const results = items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
)
// Update cache
setCache(prev => ({
...prev,
[query]: {
results,
timestamp: Date.now()
}
}))
setSearchResults(results)
} catch (error) {
console.error('Search failed:', error)
setSearchResults([])
} finally {
setIsSearching(false)
}
}, [items, cache])
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
performSearch(query)
}, [performSearch])
return (
<Card>
<CardContent className="space-y-4">
<SearchBar
placeholder="Search with caching..."
onSearch={handleSearch}
/>
{isSearching && (
<div className="text-sm text-muted-foreground">
Searching...
</div>
)}
<div className="space-y-2">
{searchResults.map(item => (
<div key={item.id} className="p-2 border rounded">
{item.name}
</div>
))}
</div>
{searchQuery && !isSearching && searchResults.length === 0 && (
<div className="text-sm text-muted-foreground">
No results found
</div>
)}
</CardContent>
</Card>
)
}
Composition Patterns
Higher-Order Components
Create HOCs to enhance components with common functionality:
import { ComponentType, useState, useCallback } from 'react'
import { SearchBar, Calendar } from '@foundrykit/components'
import { Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
interface WithSearchProps {
onSearch: (query: string) => void
searchPlaceholder?: string
}
function withSearch<T extends object>(
WrappedComponent: ComponentType<T>
) {
return function WithSearchComponent(props: T & WithSearchProps) {
const { onSearch, searchPlaceholder = "Search...", ...rest } = props
return (
<div className="space-y-4">
<SearchBar
placeholder={searchPlaceholder}
onSearch={onSearch}
/>
<WrappedComponent {...(rest as T)} />
</div>
)
}
}
interface WithDateFilterProps {
onDateChange: (date: Date | undefined) => void
selectedDate?: Date
}
function withDateFilter<T extends object>(
WrappedComponent: ComponentType<T>
) {
return function WithDateFilterComponent(props: T & WithDateFilterProps) {
const { onDateChange, selectedDate, ...rest } = props
return (
<div className="space-y-4">
<Calendar
mode="single"
selected={selectedDate}
onSelect={onDateChange}
className="rounded-md border"
/>
<WrappedComponent {...(rest as T)} />
</div>
)
}
}
// Usage
const DataList = ({ data }: { data: any[] }) => (
<div className="space-y-2">
{data.map(item => (
<div key={item.id} className="p-2 border rounded">
{item.name}
</div>
))}
</div>
)
const SearchableDataList = withSearch(DataList)
const DateFilteredDataList = withDateFilter(SearchableDataList)
function EnhancedDataList() {
const [searchQuery, setSearchQuery] = useState('')
const [selectedDate, setSelectedDate] = useState<Date>()
const [data, setData] = useState<any[]>([])
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
// Filter data based on search query
}, [])
const handleDateChange = useCallback((date: Date | undefined) => {
setSelectedDate(date)
// Filter data based on date
}, [])
return (
<Card>
<CardHeader>
<CardTitle>Enhanced Data List</CardTitle>
</CardHeader>
<CardContent>
<DateFilteredDataList
data={data}
onSearch={handleSearch}
onDateChange={handleDateChange}
selectedDate={selectedDate}
searchPlaceholder="Search data..."
/>
</CardContent>
</Card>
)
}
Compound Components
Create compound components for flexible composition:
import { createContext, useContext, useState, ReactNode } from 'react'
import { SearchBar, Calendar, Pagination } from '@foundrykit/components'
import { Card, CardContent, CardHeader, CardTitle } from '@foundrykit/primitives'
interface DataGridContextType {
searchQuery: string
selectedDate: Date | undefined
currentPage: number
setSearchQuery: (query: string) => void
setSelectedDate: (date: Date | undefined) => void
setCurrentPage: (page: number) => void
}
const DataGridContext = createContext<DataGridContextType | undefined>(undefined)
function useDataGrid() {
const context = useContext(DataGridContext)
if (!context) {
throw new Error('DataGrid components must be used within DataGrid')
}
return context
}
interface DataGridProps {
children: ReactNode
}
function DataGrid({ children }: DataGridProps) {
const [searchQuery, setSearchQuery] = useState('')
const [selectedDate, setSelectedDate] = useState<Date>()
const [currentPage, setCurrentPage] = useState(1)
return (
<DataGridContext.Provider value={{
searchQuery,
selectedDate,
currentPage,
setSearchQuery,
setSelectedDate,
setCurrentPage
}}>
<Card>
<CardContent className="space-y-4">
{children}
</CardContent>
</Card>
</DataGridContext.Provider>
)
}
function DataGridSearch({ placeholder = "Search..." }: { placeholder?: string }) {
const { searchQuery, setSearchQuery } = useDataGrid()
return (
<SearchBar
placeholder={placeholder}
onSearch={setSearchQuery}
/>
)
}
function DataGridDateFilter() {
const { selectedDate, setSelectedDate } = useDataGrid()
return (
<Calendar
mode="single"
selected={selectedDate}
onSelect={setSelectedDate}
className="rounded-md border"
/>
)
}
function DataGridPagination({ totalPages }: { totalPages: number }) {
const { currentPage, setCurrentPage } = useDataGrid()
return (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
)
}
function DataGridContent({ children }: { children: ReactNode }) {
return <div className="space-y-2">{children}</div>
}
// Usage
function CompoundDataGrid() {
return (
<DataGrid>
<DataGridSearch placeholder="Search items..." />
<DataGridDateFilter />
<DataGridContent>
{/* Your data content here */}
<div>Data items...</div>
</DataGridContent>
<DataGridPagination totalPages={10} />
</DataGrid>
)
}
Error Handling Patterns
Error Boundaries with Components
Implement error boundaries for component error handling:
import { Component, ReactNode } from 'react'
import { SearchBar, Calendar } from '@foundrykit/components'
import { Card, CardContent, Button } from '@foundrykit/primitives'
interface ErrorBoundaryState {
hasError: boolean
error?: Error
}
interface ErrorBoundaryProps {
children: ReactNode
fallback?: ReactNode
}
class ComponentErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: any) {
console.error('Component error:', error, errorInfo)
}
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback
}
return (
<Card>
<CardContent className="text-center space-y-4">
<h3 className="text-lg font-semibold">Something went wrong</h3>
<p className="text-muted-foreground">
{this.state.error?.message || 'An unexpected error occurred'}
</p>
<Button
onClick={() => this.setState({ hasError: false, error: undefined })}
>
Try Again
</Button>
</CardContent>
</Card>
)
}
return this.props.children
}
}
function SafeComponentWrapper({ children }: { children: ReactNode }) {
return (
<ComponentErrorBoundary
fallback={
<Card>
<CardContent className="text-center">
<p>Component failed to load</p>
</CardContent>
</Card>
}
>
{children}
</ComponentErrorBoundary>
)
}
function SafeSearchInterface() {
return (
<SafeComponentWrapper>
<div className="space-y-4">
<SearchBar placeholder="Search..." onSearch={console.log} />
<Calendar mode="single" />
</div>
</SafeComponentWrapper>
)
}
Next Steps
- Review best practices for optimal usage patterns
- Learn about integration patterns with primitives
- Explore component reference for detailed usage examples