skills/futuretea/x-project/React Development

React Development

Installation
SKILL.md

React Development

React is a JavaScript library for building user interfaces using components.

Core Concepts

Component Types

Function Components (Recommended):

// Basic component
interface ButtonProps {
  children: React.ReactNode
  onClick?: () => void
  variant?: 'primary' | 'secondary'
  disabled?: boolean
}

export function Button({
  children,
  onClick,
  variant = 'primary',
  disabled = false,
}: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {children}
    </button>
  )
}

// Component with forwardRef
import { forwardRef } from 'react'

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string
  error?: string
}

export const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ label, error, ...props }, ref) => {
    return (
      <div className="form-field">
        <label>{label}</label>
        <input ref={ref} {...props} />
        {error && <span className="error">{error}</span>}
      </div>
    )
  }
)
Input.displayName = 'Input'

// Generic component
interface ListProps<T> {
  items: T[]
  renderItem: (item: T) => React.ReactNode
  keyExtractor: (item: T) => string | number
}

export function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  )
}

// Usage
<List
  items={users}
  keyExtractor={(user) => user.id}
  renderItem={(user) => <UserCard user={user} />}
/>

Hooks

useState:

import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  const [user, setUser] = useState<User | null>(null)

  // Functional update
  const increment = () => setCount((prev) => prev + 1)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  )
}

// With lazy initialization
function ExpensiveComponent() {
  const [data, setData] = useState(() => computeExpensiveValue())
  // ...
}

useEffect:

import { useEffect, useState } from 'react'

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(true)

  // Fetch on mount and when userId changes
  useEffect(() => {
    let cancelled = false

    async function fetchUser() {
      setLoading(true)
      try {
        const data = await api.getUser(userId)
        if (!cancelled) {
          setUser(data)
        }
      } finally {
        if (!cancelled) {
          setLoading(false)
        }
      }
    }

    fetchUser()

    // Cleanup
    return () => {
      cancelled = true
    }
  }, [userId])

  // Subscribe to event
  useEffect(() => {
    function handleResize() {
      console.log('Resized')
    }

    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('resize', handleResize)
    }
  }, [])

  if (loading) return <Loading />
  if (!user) return <NotFound />

  return <UserCard user={user} />
}

useContext:

import { createContext, useContext, useState } from 'react'

interface AuthContextValue {
  user: User | null
  login: (email: string, password: string) => Promise<void>
  logout: () => void
  isLoading: boolean
}

const AuthContext = createContext<AuthContextValue | null>(null)

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const [isLoading, setIsLoading] = useState(false)

  const login = async (email: string, password: string) => {
    setIsLoading(true)
    try {
      const user = await api.login(email, password)
      setUser(user)
    } finally {
      setIsLoading(false)
    }
  }

  const logout = () => {
    api.logout()
    setUser(null)
  }

  return (
    <AuthContext.Provider value={{ user, login, logout, isLoading }}>
      {children}
    </AuthContext.Provider>
  )
}

// Custom hook for using context
export function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider')
  }
  return context
}

// Usage
function Header() {
  const { user, logout } = useAuth()

  return (
    <header>
      {user ? (
        <>
          <span>Welcome, {user.name}</span>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <Link to="/login">Login</Link>
      )}
    </header>
  )
}

useReducer:

import { useReducer } from 'react'

interface State {
  count: number
  step: number
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'setStep'; payload: number }
  | { type: 'reset' }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step }
    case 'decrement':
      return { ...state, count: state.count - state.step }
    case 'setStep':
      return { ...state, step: action.payload }
    case 'reset':
      return { count: 0, step: 1 }
    default:
      return state
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 })

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <input
        type="number"
        value={state.step}
        onChange={(e) =>
          dispatch({ type: 'setStep', payload: Number(e.target.value) })
        }
      />
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  )
}

useMemo and useCallback:

import { useMemo, useCallback, useState } from 'react'

function DataTable({ data, filter }: { data: Item[]; filter: string }) {
  const [sortKey, setSortKey] = useState<keyof Item>('name')

  // Memoize expensive computation
  const filteredAndSortedData = useMemo(() => {
    return data
      .filter((item) => item.name.includes(filter))
      .sort((a, b) => String(a[sortKey]).localeCompare(String(b[sortKey])))
  }, [data, filter, sortKey])

  // Memoize callback
  const handleSort = useCallback((key: keyof Item) => {
    setSortKey(key)
  }, [])

  return (
    <table>
      <thead>
        <tr>
          <SortableHeader sortKey="name" onSort={handleSort}>
            Name
          </SortableHeader>
          <SortableHeader sortKey="value" onSort={handleSort}>
            Value
          </SortableHeader>
        </tr>
      </thead>
      <tbody>
        {filteredAndSortedData.map((item) => (
          <Row key={item.id} item={item} />
        ))}
      </tbody>
    </table>
  )
}

Custom Hooks:

// useLocalStorage.ts
import { useState, useEffect, useCallback } from 'react'

export function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key)
      return item ? (JSON.parse(item) as T) : initialValue
    } catch (error) {
      console.error(error)
      return initialValue
    }
  })

  const setValue = useCallback(
    (value: T | ((prev: 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]
}

// useFetch.ts
import { useState, useEffect } from 'react'

interface UseFetchResult<T> {
  data: T | null
  error: Error | null
  isLoading: boolean
}

export function useFetch<T>(url: string): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null)
  const [error, setError] = useState<Error | null>(null)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    const controller = new AbortController()

    async function fetchData() {
      try {
        setIsLoading(true)
        const response = await fetch(url, { signal: controller.signal })
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        const json = await response.json()
        setData(json)
      } catch (err) {
        if (err instanceof Error && err.name !== 'AbortError') {
          setError(err)
        }
      } finally {
        setIsLoading(false)
      }
    }

    fetchData()

    return () => controller.abort()
  }, [url])

  return { data, error, isLoading }
}

// Usage
function UserProfile({ userId }: { userId: string }) {
  const { data: user, error, isLoading } = useFetch<User>(`/api/users/${userId}`)

  if (isLoading) return <Loading />
  if (error) return <Error message={error.message} />
  if (!user) return <NotFound />

  return <UserCard user={user} />
}

Patterns

Compound Components:

import { createContext, useContext, useState } from 'react'

interface TabsContextValue {
  activeTab: string
  setActiveTab: (tab: string) => void
}

const TabsContext = createContext<TabsContextValue | null>(null)

function useTabs() {
  const context = useContext(TabsContext)
  if (!context) throw new Error('Must be used within Tabs')
  return context
}

// Root component
export function Tabs({
  children,
  defaultTab,
}: {
  children: React.ReactNode
  defaultTab: string
}) {
  const [activeTab, setActiveTab] = useState(defaultTab)

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  )
}

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

// Trigger component
export function TabTrigger({
  value,
  children,
}: {
  value: string
  children: React.ReactNode
}) {
  const { activeTab, setActiveTab } = useTabs()

  return (
    <button
      className={activeTab === value ? 'active' : ''}
      onClick={() => setActiveTab(value)}
    >
      {children}
    </button>
  )
}

// Content component
export function TabContent({
  value,
  children,
}: {
  value: string
  children: React.ReactNode
}) {
  const { activeTab } = useTabs()

  if (activeTab !== value) return null

  return <div className="tab-content">{children}</div>
}

// Usage
<Tabs defaultTab="account">
  <TabList>
    <TabTrigger value="account">Account</TabTrigger>
    <TabTrigger value="password">Password</TabTrigger>
    <TabTrigger value="notifications">Notifications</TabTrigger>
  </TabList>
  <TabContent value="account">
    <AccountSettings />
  </TabContent>
  <TabContent value="password">
    <PasswordSettings />
  </TabContent>
  <TabContent value="notifications">
    <NotificationSettings />
  </TabContent>
</Tabs>

Render Props:

interface ToggleProps {
  children: (props: { on: boolean; toggle: () => void }) => React.ReactNode
  initialOn?: boolean
}

function Toggle({ children, initialOn = false }: ToggleProps) {
  const [on, setOn] = useState(initialOn)
  const toggle = () => setOn((prev) => !prev)

  return <>{children({ on, toggle })}</>
}

// Usage
<Toggle>
  {({ on, toggle }) => (
    <button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>
  )}
</Toggle>

Higher-Order Components (HOCs):

import { ComponentType } from 'react'

interface WithLoadingProps {
  isLoading: boolean
}

function withLoading<P extends object>(
  WrappedComponent: ComponentType<P>
): ComponentType<P & WithLoadingProps> {
  return function WithLoadingComponent({
    isLoading,
    ...props
  }: P & WithLoadingProps) {
    if (isLoading) return <LoadingSpinner />
    return <WrappedComponent {...(props as P)} />
  }
}

// Usage
const UserProfileWithLoading = withLoading(UserProfile)

Performance Optimization

React.memo:

import { memo } from 'react'

interface ExpensiveComponentProps {
  data: DataItem[]
  onItemClick: (id: string) => void
}

const ExpensiveComponent = memo<ExpensiveComponentProps>(
  ({ data, onItemClick }) => {
    return (
      <ul>
        {data.map((item) => (
          <li key={item.id} onClick={() => onItemClick(item.id)}>
            {item.name}
          </li>
        ))}
      </ul>
    )
  },
  // Custom comparison
  (prevProps, nextProps) => {
    return prevProps.data.length === nextProps.data.length
  }
)

ExpensiveComponent.displayName = 'ExpensiveComponent'

Code Splitting:

import { lazy, Suspense } from 'react'

// Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}

Best Practices

  1. Use TypeScript - Catch errors at compile time
  2. Keep components small - Single responsibility
  3. Lift state carefully - Avoid prop drilling
  4. Use custom hooks - Extract reusable logic
  5. Memoize when needed - But don't premature optimize
  6. Handle loading/error states - Good UX
  7. Clean up side effects - Prevent memory leaks

Additional Resources

  • references/react-patterns.md - Advanced patterns
  • examples/react-components/ - Component examples
Weekly Installs
–
First Seen
–