# React Skill

> This skill should be used when the user asks about React, React hooks, component patterns, state management, context API, React Router, React Query, Zustand, Redux Toolkit, React performance optimization, refs, portals, error boundaries, React 18/19 features, concurrent rendering, Suspense, or building React component libraries. Trigger when the user mentions "react", "useEffect", "useState", "useContext", "useMemo", "useCallback", "useRef", "react hooks", "react router", "react query", "zustand", "redux", "react component", "JSX", "react context", or any React-specific development question.

- Skill: `sirhamza/react-skill` (Agent Skill)
- Install (CLI): `npx skillmds@latest add sirhamza/react-skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/sirhamza/react-skill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: SirHamza (https://skillmd.com/u/sirhamza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/sirhamza/react-skill

---


# React Expert

## Overview

Advanced expertise in React — from core hooks and component patterns to state management, performance optimization, and React 18/19 concurrent features.

---

## 1. Core Hooks

```tsx
// useState — local state
const [count, setCount] = useState(0)
const [user, setUser] = useState<User | null>(null)

// useEffect — side effects & cleanup
useEffect(() => {
  const sub = subscribe(id, callback)
  return () => sub.unsubscribe()   // cleanup
}, [id])                            // dependency array

// useRef — mutable ref, DOM access, skip re-render
const inputRef = useRef<HTMLInputElement>(null)
const timerRef = useRef<ReturnType<typeof setInterval>>(null)

// useMemo — memoize expensive computation
const sorted = useMemo(() => [...items].sort(compareFn), [items])

// useCallback — stable function reference
const handleClick = useCallback((id: string) => {
  setSelected(id)
}, [])  // empty = stable across renders

// useReducer — complex state logic
const [state, dispatch] = useReducer(reducer, initialState)

// useContext — consume context
const theme = useContext(ThemeContext)

// useId — stable unique IDs (React 18+)
const id = useId()
```

---

## 2. Custom Hooks

```tsx
// Fetching with loading/error state
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    let cancelled = false
    setLoading(true)
    fetch(url)
      .then(r => r.json())
      .then(d => { if (!cancelled) setData(d) })
      .catch(e => { if (!cancelled) setError(e) })
      .finally(() => { if (!cancelled) setLoading(false) })
    return () => { cancelled = true }
  }, [url])

  return { data, loading, error }
}

// Debounced value
function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState(value)
  useEffect(() => {
    const t = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(t)
  }, [value, delay])
  return debounced
}

// Local storage sync
function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    try { return JSON.parse(localStorage.getItem(key) ?? '') }
    catch { return initial }
  })
  const set = useCallback((v: T) => {
    setValue(v)
    localStorage.setItem(key, JSON.stringify(v))
  }, [key])
  return [value, set] as const
}
```

---

## 3. Component Patterns

### Compound Components
```tsx
const Tabs = ({ children, defaultTab }: TabsProps) => {
  const [active, setActive] = useState(defaultTab)
  return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>
}
Tabs.List = TabList
Tabs.Tab = Tab
Tabs.Panel = TabPanel

// Usage
<Tabs defaultTab="home">
  <Tabs.List>
    <Tabs.Tab id="home">Home</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel id="home">...</Tabs.Panel>
</Tabs>
```

### Render Props
```tsx
function MouseTracker({ render }: { render: (pos: Position) => ReactNode }) {
  const [pos, setPos] = useState({ x: 0, y: 0 })
  return <div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>{render(pos)}</div>
}
```

### Controlled vs Uncontrolled
```tsx
// Controlled — React owns the value
<input value={value} onChange={e => setValue(e.target.value)} />

// Uncontrolled — DOM owns the value, use ref to read
const ref = useRef<HTMLInputElement>(null)
<input defaultValue="initial" ref={ref} />
```

### Forwarding Refs
```tsx
const Input = forwardRef<HTMLInputElement, InputProps>(({ label, ...props }, ref) => (
  <label>
    {label}
    <input ref={ref} {...props} />
  </label>
))
```

---

## 4. State Management

### Context API (light global state)
```tsx
interface AuthContextType { user: User | null; logout: () => void }
const AuthContext = createContext<AuthContextType | null>(null)

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const logout = useCallback(() => setUser(null), [])
  return <AuthContext.Provider value={{ user, logout }}>{children}</AuthContext.Provider>
}

export function useAuth() {
  const ctx = useContext(AuthContext)
  if (!ctx) throw new Error('useAuth must be used inside AuthProvider')
  return ctx
}
```

### Zustand (recommended for most apps)
```tsx
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface CartStore {
  items: CartItem[]
  addItem: (item: CartItem) => void
  removeItem: (id: string) => void
  total: () => number
}

const useCartStore = create<CartStore>()(
  persist(
    (set, get) => ({
      items: [],
      addItem: item => set(s => ({ items: [...s.items, item] })),
      removeItem: id => set(s => ({ items: s.items.filter(i => i.id !== id) })),
      total: () => get().items.reduce((sum, i) => sum + i.price, 0),
    }),
    { name: 'cart' }
  )
)
```

### Redux Toolkit
```tsx
import { createSlice, createAsyncThunk, configureStore } from '@reduxjs/toolkit'

const fetchUser = createAsyncThunk('user/fetch', async (id: string) => {
  const res = await fetch(`/api/users/${id}`)
  return res.json() as Promise<User>
})

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null as User | null, status: 'idle' },
  reducers: {
    clearUser: state => { state.data = null },
  },
  extraReducers: builder => {
    builder
      .addCase(fetchUser.fulfilled, (state, action) => { state.data = action.payload; state.status = 'success' })
      .addCase(fetchUser.pending, state => { state.status = 'loading' })
  }
})
```

---

## 5. Data Fetching — TanStack Query (React Query)

```tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

// Query
const { data, isLoading, error } = useQuery({
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
  staleTime: 5 * 60 * 1000,  // 5 min
  enabled: !!id,
})

// Mutation with optimistic update
const queryClient = useQueryClient()
const mutation = useMutation({
  mutationFn: updateUser,
  onMutate: async newUser => {
    await queryClient.cancelQueries({ queryKey: ['user', newUser.id] })
    const prev = queryClient.getQueryData(['user', newUser.id])
    queryClient.setQueryData(['user', newUser.id], newUser)
    return { prev }
  },
  onError: (_, __, ctx) => {
    queryClient.setQueryData(['user', _.id], ctx?.prev)
  },
  onSettled: (_, __, vars) => {
    queryClient.invalidateQueries({ queryKey: ['user', vars.id] })
  },
})
```

---

## 6. React 18 / 19 Features

```tsx
// Concurrent rendering — startTransition (non-urgent updates)
import { startTransition, useTransition, useDeferredValue } from 'react'

const [isPending, startTransition] = useTransition()
startTransition(() => setSearchQuery(input))  // won't block UI

// useDeferredValue — defer expensive re-renders
const deferred = useDeferredValue(searchQuery)

// Suspense for data fetching (with React Query / SWR)
<Suspense fallback={<Spinner />}>
  <UserProfile id={id} />
</Suspense>

// React 19 — use() hook
import { use } from 'react'
function UserName({ promise }: { promise: Promise<User> }) {
  const user = use(promise)  // suspends until resolved
  return <span>{user.name}</span>
}

// React 19 — Actions (form handling)
function Form() {
  const [state, formAction, isPending] = useActionState(submitAction, null)
  return <form action={formAction}>...</form>
}

// React 19 — useOptimistic
const [optimisticLikes, addOptimisticLike] = useOptimistic(likes, (state, n) => state + n)
```

---

## 7. Performance Optimization

```tsx
// React.memo — skip re-render if props unchanged
const ExpensiveList = memo(({ items }: { items: Item[] }) => (
  <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>
))

// Avoid inline object/array props (new reference every render)
// Bad:
<List style={{ margin: 0 }} items={[1, 2, 3]} />
// Good:
const style = useMemo(() => ({ margin: 0 }), [])
const items = useMemo(() => [1, 2, 3], [])

// Virtualize long lists — @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual'

// Code splitting — lazy + Suspense
const Dashboard = lazy(() => import('./Dashboard'))

// Profiler — measure render costs
<Profiler id="App" onRender={(id, phase, actualDuration) => console.log(id, phase, actualDuration)}>
  <App />
</Profiler>
```

---

## 8. Error Boundaries

```tsx
// Class component (still required for error boundaries)
class ErrorBoundary extends Component<{ fallback: ReactNode; children: ReactNode }, { error: Error | null }> {
  state = { error: null }
  static getDerivedStateFromError(error: Error) { return { error } }
  componentDidCatch(error: Error, info: ErrorInfo) { logError(error, info) }
  render() {
    return this.state.error ? this.props.fallback : this.props.children
  }
}

// react-error-boundary library (recommended)
import { ErrorBoundary } from 'react-error-boundary'
<ErrorBoundary fallbackRender={({ error, resetErrorBoundary }) => (
  <div>Error: {error.message} <button onClick={resetErrorBoundary}>Retry</button></div>
)}>
  <App />
</ErrorBoundary>
```

---

## 9. React Router v6/v7

```tsx
import { createBrowserRouter, RouterProvider, Outlet, useParams, useNavigate, useLoaderData } from 'react-router-dom'

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <Home /> },
      {
        path: 'users/:id',
        element: <UserDetail />,
        loader: async ({ params }) => fetchUser(params.id!),
      },
      { path: '*', element: <NotFound /> },
    ],
  },
])

// In component
const { id } = useParams()
const user = useLoaderData() as User
const navigate = useNavigate()
navigate('/users/123', { replace: true })
```

---

## 10. TypeScript Patterns

```tsx
// Component prop types
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary'
  loading?: boolean
}

// Generic component
function List<T extends { id: string }>({ items, renderItem }: {
  items: T[]
  renderItem: (item: T) => ReactNode
}) {
  return <ul>{items.map(item => <li key={item.id}>{renderItem(item)}</li>)}</ul>
}

// Event handlers
const onChange: React.ChangeEventHandler<HTMLInputElement> = e => setValue(e.target.value)
const onSubmit: React.FormEventHandler<HTMLFormElement> = e => { e.preventDefault(); ... }

// Discriminated unions for component state
type State =
  | { status: 'loading' }
  | { status: 'error'; error: Error }
  | { status: 'success'; data: User }
```

---

## Core Competency Summary

- Build composable, type-safe React components with TypeScript
- Master hooks: `useState`, `useEffect`, `useMemo`, `useCallback`, `useRef`, custom hooks
- Manage state with Zustand, Context API, or Redux Toolkit
- Fetch and cache data with TanStack Query
- Leverage React 18/19 concurrent features: `Suspense`, `useTransition`, `useOptimistic`
- Optimize performance: `memo`, `useMemo`, `useCallback`, code splitting, virtualization
- Handle routing with React Router v6/v7

