| name | react-component-design |
| description | Design React components following Tetris architecture guidelines. Use when creating new components, refactoring components, optimizing performance, or improving component structure. Auto-triggers on phrases like "create a component", "refactor this component", "optimize rendering", or "improve component design". Emphasizes component consolidation, unified patterns, and efficient hook usage. |
| allowed-tools | Read, Edit, Grep, Glob |
React Component Design Guide
Design patterns for React components in the Tetris project.
Core Principles
- Component Consolidation: Prefer unified components over excessive fragmentation
- Functional Components: No class components
- Custom Hooks: Extract reusable logic
- Type Aliases: Use
typeinstead ofinterfacefor props - Dynamic IDs: Use
useId()hook for accessibility - Optimized Patterns: Minimize re-renders and prop drilling
Component Structure
Basic Component Template
import { useId } from 'react'
import { useTranslation } from 'react-i18next'
type GameButtonProps = {
onClick: () => void
label: string
disabled?: boolean
}
export const GameButton = ({ onClick, label, disabled = false }: GameButtonProps) => {
const { t } = useTranslation()
const id = useId()
return (
<button
id={id}
onClick={onClick}
disabled={disabled}
className="game-button"
>
{t(label)}
</button>
)
}
Component with Custom Hook
// Custom hook for game logic
const useGameState = () => {
const [board, setBoard] = useState<Board>(createBoard())
const [currentPiece, setCurrentPiece] = useState<Piece | null>(null)
const [score, setScore] = useState(0)
const placePiece = useCallback((position: Position) => {
if (!currentPiece) return
const result = tryPlacePiece(board, currentPiece, position)
if (result.ok) {
setBoard(result.value.board)
setScore((s) => s + result.value.points)
}
}, [board, currentPiece])
return { board, currentPiece, score, placePiece }
}
// Component using the hook
export const GameBoard = () => {
const { board, currentPiece, score, placePiece } = useGameState()
return (
<div className="game-board">
<Board data={board} currentPiece={currentPiece} />
<Score value={score} />
</div>
)
}
Design Patterns
1. Component Consolidation
// ❌ Excessive fragmentation
const Header = () => <div className="header">...</div>
const HeaderTitle = () => <h1>...</h1>
const HeaderSubtitle = () => <p>...</p>
const HeaderActions = () => <div>...</div>
// ✅ Consolidated component
type GameHeaderProps = {
title: string
subtitle: string
actions: ReactNode
}
export const GameHeader = ({ title, subtitle, actions }: GameHeaderProps) => (
<header className="game-header">
<div>
<h1>{title}</h1>
<p>{subtitle}</p>
</div>
<div className="actions">{actions}</div>
</header>
)
2. Composition over Props Drilling
// ❌ Props drilling
<Game>
<Board score={score} level={level} />
<Controls score={score} level={level} />
<Stats score={score} level={level} />
</Game>
// ✅ Context for shared state
const GameStateContext = createContext<GameState | null>(null)
export const GameProvider = ({ children }: { children: ReactNode }) => {
const gameState = useGameState()
return (
<GameStateContext.Provider value={gameState}>
{children}
</GameStateContext.Provider>
)
}
// Usage
<GameProvider>
<Board />
<Controls />
<Stats />
</GameProvider>
3. Memoization for Performance
// ✅ Memoize expensive components
export const Board = memo(({ cells, currentPiece }: BoardProps) => {
return (
<div className="board">
{cells.map((row, y) => (
<Row key={y} cells={row} y={y} currentPiece={currentPiece} />
))}
</div>
)
})
// ✅ Memoize callbacks
const GameControls = () => {
const { movePiece, rotatePiece } = useGame()
const handleLeft = useCallback(() => {
movePiece('LEFT')
}, [movePiece])
const handleRotate = useCallback(() => {
rotatePiece()
}, [rotatePiece])
return (
<div>
<button onClick={handleLeft}>{t('controls.left')}</button>
<button onClick={handleRotate}>{t('controls.rotate')}</button>
</div>
)
}
4. Type-safe Props
// ✅ Use type aliases (not interface)
type ButtonVariant = 'primary' | 'secondary' | 'danger'
type ButtonProps = {
variant: ButtonVariant
size?: 'small' | 'medium' | 'large'
onClick: () => void
children: ReactNode
disabled?: boolean
}
export const Button = ({
variant,
size = 'medium',
onClick,
children,
disabled = false
}: ButtonProps) => {
const className = cn(
'button',
`button--${variant}`,
`button--${size}`,
disabled && 'button--disabled'
)
return (
<button className={className} onClick={onClick} disabled={disabled}>
{children}
</button>
)
}
5. Dynamic IDs for Accessibility
// ❌ Static IDs
export const FormField = ({ label }: FormFieldProps) => (
<div>
<label htmlFor="game-input">{label}</label>
<input id="game-input" />
</div>
)
// ✅ Dynamic IDs with useId()
export const FormField = ({ label }: FormFieldProps) => {
const id = useId()
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} />
</div>
)
}
Custom Hooks Best Practices
1. Extract Reusable Logic
// useKeyPress hook
export const useKeyPress = (targetKey: string, callback: () => void) => {
useEffect(() => {
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key === targetKey) {
callback()
}
}
window.addEventListener('keydown', handleKeyPress)
return () => {
window.removeEventListener('keydown', handleKeyPress)
}
}, [targetKey, callback])
}
// Usage in component
const GameControls = () => {
const { movePiece } = useGame()
useKeyPress('ArrowLeft', () => movePiece('LEFT'))
useKeyPress('ArrowRight', () => movePiece('RIGHT'))
useKeyPress(' ', () => movePiece('DROP'))
return <div>...</div>
}
2. State Management Hooks
// useLocalStorage hook
export const useLocalStorage = <T,>(
key: string,
initialValue: T
): [T, (value: T) => void] => {
const [storedValue, setStoredValue] = useState<T>(() => {
const item = localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
})
const setValue = useCallback((value: T) => {
setStoredValue(value)
localStorage.setItem(key, JSON.stringify(value))
}, [key])
return [storedValue, setValue]
}
// Usage
const [highScore, setHighScore] = useLocalStorage('tetris:highScore', 0)
Component Checklist
- Functional component (no classes)
- Type alias for props (not interface)
- i18n for all user-facing strings
- useId() for dynamic IDs
- Custom hooks for reusable logic
- Memoization where appropriate
- Minimal prop drilling (use context)
- Consolidated (not overly fragmented)
Performance Optimization
// ✅ Optimize with memo and useMemo
export const ExpensiveComponent = memo(({ data }: Props) => {
const processedData = useMemo(() => {
return expensiveCalculation(data)
}, [data])
return <div>{processedData}</div>
})
// ✅ Optimize callbacks with useCallback
const Parent = () => {
const [count, setCount] = useState(0)
const increment = useCallback(() => {
setCount((c) => c + 1)
}, [])
return <Child onIncrement={increment} />
}
When This Skill Activates
- "Create a new component"
- "Refactor this component"
- "Optimize component performance"
- "Improve component structure"
- "Extract logic to custom hook"
- "Reduce re-renders"
Advanced Patterns
See component-patterns.md for:
- Advanced composition patterns
- Custom hook recipes
- Performance optimization strategies
- Accessibility best practices