- codegen: Low-code React app with JSON-driven component system - packagerepo: Schema-driven package repository with backend/frontend - postgres: Next.js app with Drizzle ORM and PostgreSQL Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
10 KiB
Hook Library Documentation
Overview
The CodeForge hook library provides a comprehensive set of reusable React hooks organized by purpose. All business logic should be extracted into hooks, keeping components under 150 LOC and focused purely on presentation.
Directory Structure
src/hooks/
├── core/ # Core utility hooks
├── ui/ # UI state management hooks
├── config/ # Configuration and page management hooks
├── features/ # Feature-specific business logic hooks
├── ai/ # AI-powered functionality hooks
└── validation/ # Validation and error checking hooks
Core Hooks
use-kv-state.ts
Enhanced wrapper around Spark's useKV with Zod validation support.
Usage:
import { useKVState } from '@/hooks/core/use-kv-state'
import { z } from 'zod'
const UserSchema = z.object({
name: z.string(),
age: z.number().min(0),
})
const [user, setUser] = useKVState(
'user-data',
{ name: '', age: 0 },
UserSchema
)
// Invalid data is rejected automatically
setUser({ name: 'John', age: -5 }) // Logs error, keeps previous value
setUser({ name: 'John', age: 25 }) // Valid, updates state
use-debounced-save.ts
Automatically debounces and saves values after a delay.
Usage:
import { useDebouncedSave } from '@/hooks/core/use-debounced-save'
import { useKV } from '@github/spark/hooks'
const [code, setCode] = useState('')
const [, saveCode] = useKV('code-content', '')
useDebouncedSave(code, saveCode, 1000) // Saves 1s after last change
use-clipboard.ts
Copy/paste operations with user feedback.
Usage:
import { useClipboard } from '@/hooks/core/use-clipboard'
const { copy, paste, copied } = useClipboard()
<Button
onClick={() => copy(codeContent, 'Code copied!')}
disabled={copied}
>
{copied ? 'Copied!' : 'Copy Code'}
</Button>
UI Hooks
use-dialog.ts
Manage dialog open/closed state.
Usage:
import { useDialog } from '@/hooks/ui/use-dialog'
const { open, openDialog, closeDialog, toggleDialog } = useDialog()
<Button onClick={openDialog}>Open Settings</Button>
<Dialog open={open} onOpenChange={closeDialog}>
{/* Dialog content */}
</Dialog>
use-selection.ts
Multi-select state management.
Usage:
import { useSelection } from '@/hooks/ui/use-selection'
const {
selected,
toggle,
selectAll,
clear,
isSelected,
count
} = useSelection<string>()
files.map(file => (
<Checkbox
checked={isSelected(file.id)}
onCheckedChange={() => toggle(file.id)}
/>
))
<Button onClick={() => selectAll(files.map(f => f.id))}>
Select All ({count} selected)
</Button>
use-confirmation.ts
Confirmation dialog state and actions.
Usage:
import { useConfirmation } from '@/hooks/ui/use-confirmation'
import { AlertDialog } from '@/components/ui/alert-dialog'
const { state, confirm, handleConfirm, handleCancel } = useConfirmation()
const deleteFile = (fileId: string) => {
confirm(
'Delete File?',
'This action cannot be undone.',
() => {
// Perform deletion
setFiles(files.filter(f => f.id !== fileId))
}
)
}
<AlertDialog open={state.open} onOpenChange={handleCancel}>
<AlertDialogContent>
<AlertDialogTitle>{state.title}</AlertDialogTitle>
<AlertDialogDescription>{state.description}</AlertDialogDescription>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancel}>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirm}>Confirm</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Config Hooks
use-page-config.ts
Load and manage page configurations from KV store.
Usage:
import { usePageConfig, usePageRegistry } from '@/hooks/config/use-page-config'
// Get specific page config
const { pageConfig } = usePageConfig('code-editor')
console.log(pageConfig.title) // "Code Editor"
// Get all pages
const { pages, getPage } = usePageRegistry()
const dashboardPage = getPage('dashboard')
use-layout-state.ts
Persist layout state (panel sizes, collapsed state) per page.
Usage:
import { useLayoutState } from '@/hooks/config/use-layout-state'
const { layoutState, setPanelSizes, setCollapsed, setActivePanel } =
useLayoutState('code-editor')
<ResizablePanelGroup
onLayout={setPanelSizes}
defaultLayout={layoutState.panelSizes}
>
{/* Panels */}
</ResizablePanelGroup>
use-feature-flags.ts
Runtime feature flag management.
Usage:
import { useFeatureFlags } from '@/hooks/config/use-feature-flags'
const { isEnabled, enable, disable, toggle } = useFeatureFlags()
{isEnabled('ai-improve') && (
<Button onClick={handleAIImprove}>
AI Improve Code
</Button>
)}
<Switch
checked={isEnabled('dark-mode')}
onCheckedChange={() => toggle('dark-mode')}
/>
Component Size Guidelines
Rules
- Maximum 150 LOC per component file
- Extract all business logic into hooks
- Pure presentation in components
- Compose smaller components instead of conditional rendering
Example: Breaking Down a Large Component
Before (300+ LOC):
// ModelDesigner.tsx - 350 LOC
export function ModelDesigner() {
const [models, setModels] = useKV('models', [])
const [selectedModel, setSelectedModel] = useState(null)
const [showDialog, setShowDialog] = useState(false)
const handleAddModel = async () => {
// 50 lines of logic
}
const handleGenerateWithAI = async () => {
// 50 lines of AI logic
}
return (
<div>
{/* 200 lines of JSX */}
</div>
)
}
After (<150 LOC each):
// hooks/features/use-model-manager.ts - 80 LOC
export function useModelManager() {
const [models, setModels] = useKV('models', [])
const [selectedModel, setSelectedModel] = useState(null)
const addModel = useCallback(async (model) => {
// Business logic
}, [])
const generateWithAI = useCallback(async (description) => {
// AI logic
}, [])
return { models, selectedModel, setSelectedModel, addModel, generateWithAI }
}
// components/ModelList.tsx - 80 LOC
export function ModelList({ models, onSelect, selected }) {
return (
<ScrollArea>
{models.map(model => (
<ModelCard
key={model.id}
model={model}
selected={selected?.id === model.id}
onClick={() => onSelect(model)}
/>
))}
</ScrollArea>
)
}
// components/ModelEditor.tsx - 120 LOC
export function ModelEditor({ model, onChange }) {
return (
<Card>
{/* Editing UI */}
</Card>
)
}
// ModelDesigner.tsx - 90 LOC
export function ModelDesigner() {
const { models, selectedModel, setSelectedModel, addModel, generateWithAI } =
useModelManager()
const { open, openDialog, closeDialog } = useDialog()
return (
<div className="flex gap-4">
<ModelList
models={models}
selected={selectedModel}
onSelect={setSelectedModel}
/>
{selectedModel && (
<ModelEditor
model={selectedModel}
onChange={handleChange}
/>
)}
<Dialog open={open} onOpenChange={closeDialog}>
{/* AI Dialog */}
</Dialog>
</div>
)
}
JSON-Based Page Orchestration
Page Config Schema
Pages can be defined in JSON and stored in the KV database:
{
"id": "code-editor",
"title": "Code Editor",
"description": "Edit project files with AI assistance",
"icon": "Code",
"component": "CodeEditorPage",
"layout": {
"type": "split",
"direction": "horizontal",
"defaultSizes": [20, 80],
"panels": [
{
"id": "file-tree",
"component": "FileExplorer",
"minSize": 15,
"maxSize": 40
},
{
"id": "editor",
"component": "CodeEditor",
"minSize": 60
}
]
},
"features": [
{ "id": "ai-improve", "enabled": true },
{ "id": "ai-explain", "enabled": true }
],
"shortcuts": [
{ "key": "2", "ctrl": true, "action": "navigate" }
]
}
Benefits
- Runtime Configuration: Change page layouts without code changes
- User Customization: Users can configure their own layouts
- Feature Flags: Enable/disable features per page
- A/B Testing: Test different layouts easily
- Persistence: Layouts save automatically to KV store
Migration Checklist
When refactoring a large component:
- Identify business logic vs presentation
- Extract business logic into hooks (
/hooks/features/) - Break JSX into smaller components (<150 LOC each)
- Create atomic components where applicable
- Add component to page config JSON
- Update component registry
- Test isolated components
- Update documentation
Best Practices
- One Hook, One Responsibility: Each hook should manage one concern
- Hooks Return Objects: Return
{ value, actions }for clarity - Memoize Callbacks: Use
useCallbackfor functions passed as props - Type Everything: Full TypeScript types for all hooks
- Test Hooks: Write unit tests for hook logic
- Document Hooks: Add JSDoc comments explaining purpose and usage
- Validate with Zod: Use schemas for complex data structures
- Handle Loading/Error States: Return
{ data, loading, error }pattern
Next Steps
- Extract business logic from top 5 largest components
- Break down FeatureIdeaCloud (829 LOC) into 6 smaller components
- Create remaining feature hooks (file-manager, workflow-manager, etc.)
- Build PageOrchestrator component to render from JSON configs
- Add UI for editing page layouts
- Migrate all pages to JSON configuration system
Resources
- REFACTOR_PHASE2.md - Complete refactoring plan
- React Hooks Documentation
- Zod Documentation
- Atomic Design Methodology