react-component-design

React Component Design Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-component-design" with this command: npx skills add sakataka/tetris-game2/sakataka-tetris-game2-react-component-design

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 type instead of interface for 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> )

  1. 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>

  1. 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> ) }

  1. 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> ) }

  1. 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> }

  1. 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"

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

i18n-completeness

No summary provided by upstream source.

Repository SourceNeeds Review
General

architecture-compliance

No summary provided by upstream source.

Repository SourceNeeds Review
General

build-pipeline

No summary provided by upstream source.

Repository SourceNeeds Review