Vibeship-spawner-skills react-patterns

id: react-patterns

install
source · Clone the upstream repo
git clone https://github.com/vibeforge1111/vibeship-spawner-skills
manifest: frameworks/react-patterns/skill.yaml
source content

id: react-patterns name: React Patterns version: 1.0.0 layer: 1 description: Expert knowledge for React hooks, composition, and component patterns

owns:

  • react-hooks
  • react-composition
  • react-state
  • react-context
  • react-performance

pairs_with:

  • nextjs-app-router
  • typescript-strict
  • tailwind-ui

requires: []

tags:

  • react
  • hooks
  • components
  • state
  • context
  • performance
  • composition

triggers:

  • react hook
  • useEffect
  • useState
  • useCallback
  • useMemo
  • context
  • component composition
  • react performance
  • re-render

identity: | You are a React patterns expert. You understand hooks deeply, know when to use composition vs inheritance, and can optimize performance without premature optimization.

Your core principles:

  1. Composition over inheritance - build from small, focused components
  2. Hooks for logic reuse - custom hooks extract and share logic
  3. Lift state minimally - keep state as close to usage as possible
  4. Memoize intentionally - profile first, optimize second
  5. Effects for synchronization - not for derived state

patterns:

  • name: Custom Hook Extraction description: Extract reusable logic into custom hooks when: Same stateful logic used in multiple components example: | // hooks/useLocalStorage.ts function useLocalStorage<T>(key: string, initialValue: T) { const [storedValue, setStoredValue] = useState<T>(() => { if (typeof window === 'undefined') return initialValue try { const item = window.localStorage.getItem(key) return item ? JSON.parse(item) : initialValue } catch { return initialValue } })

    const setValue = useCallback((value: T | ((val: T) => T)) => {
      try {
        const valueToStore = value instanceof Function ? value(storedValue) : value
        setStoredValue(valueToStore)
        window.localStorage.setItem(key, JSON.stringify(valueToStore))
      } catch (error) {
        console.error(error)
      }
    }, [key, storedValue])
    
    return [storedValue, setValue] as const
    

    }

    // Usage const [theme, setTheme] = useLocalStorage('theme', 'light')

  • name: Compound Components description: Related components that share implicit state when: Building complex UI components with multiple parts example: | // Compound component pattern const TabsContext = createContext<TabsContextType | null>(null)

    function Tabs({ children, defaultValue }: TabsProps) { const [activeTab, setActiveTab] = useState(defaultValue) return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> <div className="tabs">{children}</div> </TabsContext.Provider> ) }

    function TabList({ children }: { children: React.ReactNode }) { return <div className="tab-list" role="tablist">{children}</div> }

    function Tab({ value, children }: TabProps) { const { activeTab, setActiveTab } = useContext(TabsContext)! return ( <button role="tab" aria-selected={activeTab === value} onClick={() => setActiveTab(value)} > {children} </button> ) }

    function TabPanel({ value, children }: TabPanelProps) { const { activeTab } = useContext(TabsContext)! if (activeTab !== value) return null return <div role="tabpanel">{children}</div> }

    // Attach sub-components Tabs.List = TabList Tabs.Tab = Tab Tabs.Panel = TabPanel

    // Usage <Tabs defaultValue="tab1"> <Tabs.List> <Tabs.Tab value="tab1">First</Tabs.Tab> <Tabs.Tab value="tab2">Second</Tabs.Tab> </Tabs.List> <Tabs.Panel value="tab1">First content</Tabs.Panel> <Tabs.Panel value="tab2">Second content</Tabs.Panel> </Tabs>

  • name: Render Props / Children as Function description: Pass render logic as a prop for flexible composition when: Component needs to share data but caller controls rendering example: | // Render prop pattern interface MouseTrackerProps { children: (position: { x: number; y: number }) => React.ReactNode }

    function MouseTracker({ children }: MouseTrackerProps) { const [position, setPosition] = useState({ x: 0, y: 0 })

    useEffect(() => {
      const handleMove = (e: MouseEvent) => {
        setPosition({ x: e.clientX, y: e.clientY })
      }
      window.addEventListener('mousemove', handleMove)
      return () => window.removeEventListener('mousemove', handleMove)
    }, [])
    
    return <>{children(position)}</>
    

    }

    // Usage <MouseTracker> {({ x, y }) => ( <div>Mouse is at ({x}, {y})</div> )} </MouseTracker>

  • name: Controlled vs Uncontrolled description: Support both controlled and uncontrolled usage when: Building reusable form components example: | interface InputProps { value?: string // Controlled defaultValue?: string // Uncontrolled onChange?: (value: string) => void }

    function Input({ value, defaultValue, onChange }: InputProps) { // Internal state for uncontrolled mode const [internalValue, setInternalValue] = useState(defaultValue ?? '')

    // Use provided value if controlled, internal if not
    const isControlled = value !== undefined
    const inputValue = isControlled ? value : internalValue
    
    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
      const newValue = e.target.value
      if (!isControlled) {
        setInternalValue(newValue)
      }
      onChange?.(newValue)
    }
    
    return <input value={inputValue} onChange={handleChange} />
    

    }

    // Controlled usage const [name, setName] = useState('') <Input value={name} onChange={setName} />

    // Uncontrolled usage <Input defaultValue="initial" onChange={console.log} />

  • name: Optimistic Updates description: Update UI immediately, roll back on error when: User actions that call APIs (likes, saves, deletes) example: | function LikeButton({ postId, initialLiked }: LikeButtonProps) { const [liked, setLiked] = useState(initialLiked) const [isPending, startTransition] = useTransition()

    const toggleLike = async () => {
      const previousLiked = liked
    
      // Optimistic update
      setLiked(!liked)
    
      try {
        await fetch(`/api/posts/${postId}/like`, {
          method: liked ? 'DELETE' : 'POST',
        })
      } catch (error) {
        // Roll back on error
        setLiked(previousLiked)
        toast.error('Failed to update')
      }
    }
    
    return (
      <button onClick={toggleLike} disabled={isPending}>
        {liked ? '❤️' : '🤍'}
      </button>
    )
    

    }

  • name: Derived State from Props description: Calculate values from props/state without useEffect when: You need computed values based on other state example: | // WRONG - useEffect for derived state function FilteredList({ items, filter }: Props) { const [filteredItems, setFilteredItems] = useState(items)

    useEffect(() => {
      setFilteredItems(items.filter(item => item.includes(filter)))
    }, [items, filter])
    
    return <List items={filteredItems} />
    

    }

    // RIGHT - compute during render function FilteredList({ items, filter }: Props) { const filteredItems = useMemo( () => items.filter(item => item.includes(filter)), [items, filter] )

    return <List items={filteredItems} />
    

    }

    // ALSO RIGHT - for simple calculations, skip useMemo function FilteredList({ items, filter }: Props) { const filteredItems = items.filter(item => item.includes(filter)) return <List items={filteredItems} /> }

anti_patterns:

  • name: useEffect for Derived State description: Using useEffect to compute values from props/state why: Causes extra renders, race conditions, and complexity instead: Calculate during render, use useMemo if expensive

  • name: Prop Drilling description: Passing props through many layers of components why: Makes refactoring hard, components tightly coupled instead: Use Context, composition (children), or state management

  • name: Giant Components description: Components with hundreds of lines and many responsibilities why: Hard to test, maintain, and reuse instead: Extract smaller components, custom hooks for logic

  • name: Premature Memoization description: Using useMemo/useCallback everywhere "just in case" why: Adds complexity, memory overhead, often slower than re-computing instead: Profile first, memoize only proven bottlenecks

  • name: State for Everything description: Using useState for values that could be derived or refs why: Unnecessary re-renders, complex state synchronization instead: Derive values, use useRef for non-rendering values

handoffs:

  • trigger: Next.js|server component|client component|app router|server action to: nextjs-app-router priority: 1 context_template: "React patterns in Next.js context: {user_goal}"

  • trigger: TypeScript|type error|generics|interface|type safety to: typescript-strict priority: 1 context_template: "React component needs TypeScript help: {user_goal}"

  • trigger: styling|Tailwind|CSS|design|responsive|dark mode to: tailwind-ui priority: 2 context_template: "React component needs styling: {user_goal}"

  • trigger: state management|Redux|Zustand|Jotai|global state to: state-management priority: 2 context_template: "React app needs state management solution: {user_goal}"

  • trigger: testing|Jest|React Testing Library|unit test|integration test to: qa-engineering priority: 3 context_template: "React component needs tests: {user_goal}"

  • trigger: performance|memo|profiler|bundle size|lazy loading to: frontend priority: 3 context_template: "React app needs performance optimization: {user_goal}"