React
by @ivangdavila
Full React 19 engineering, architecture, Server Components, hooks, Zustand, TanStack Query, forms, performance, testing, production deploy.
clawhub install reactπ About This Skill
name: React slug: react version: 1.0.4 homepage: https://clawic.com/skills/react changelog: "Added React 19 coverage, Server Components, AI Mistakes section, Core Rules, state management patterns, setup system" description: Full React 19 engineering, architecture, Server Components, hooks, Zustand, TanStack Query, forms, performance, testing, production deploy.
React
Production-grade React engineering. This skill transforms how you build React applications β from component architecture to deployment.
When to Use
Architecture Decisions
Before writing code, make these decisions:
| Decision | Options | Default | |----------|---------|---------| | Rendering | SPA / SSR / Static / Hybrid | SSR (Next.js) | | State (server) | TanStack Query / SWR / use() | TanStack Query | | State (client) | useState / Zustand / Jotai | Zustand if shared | | Styling | Tailwind / CSS Modules / styled | Tailwind | | Forms | React Hook Form + Zod / native | RHF + Zod |
Rule: Server state (API data) and client state (UI state) are DIFFERENT. Never mix them.
Component Rules
// β
The correct pattern
export function UserCard({ user, onEdit }: UserCardProps) {
// 1. Hooks first (always)
const [isOpen, setIsOpen] = useState(false)
// 2. Derived state (NO useEffect for this)
const fullName = ${user.firstName} ${user.lastName}
// 3. Handlers
const handleEdit = useCallback(() => onEdit(user.id), [onEdit, user.id])
// 4. Early returns
if (!user) return null
// 5. JSX (max 50 lines)
return (...)
}
| Rule | Why | |------|-----| | Named exports only | Refactoring safety, IDE support | | Props interface exported | Reusable, documented | | Max 50 lines JSX | Extract if bigger | | Max 300 lines file | Split into components | | Hooks at top | React rules + predictable |
State Management
Is it from an API?
ββ YES β TanStack Query (NOT Redux, NOT Zustand)
ββ NO β Is it shared across components?
ββ YES β Zustand (simple) or Context (if rarely changes)
ββ NO β useState
TanStack Query (Server State)
// Query key factory β prevents key typos
export const userKeys = {
all: ['users'] as const,
detail: (id: string) => [...userKeys.all, id] as const,
}export function useUser(id: string) {
return useQuery({
queryKey: userKeys.detail(id),
queryFn: () => fetchUser(id),
staleTime: 5 * 60 * 1000, // 5 min
})
}
Zustand (Client State)
// Thin stores, one concern each
export const useUIStore = create()((set) => ({
sidebarOpen: true,
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}))// ALWAYS use selectors β prevents unnecessary rerenders
const isOpen = useUIStore((s) => s.sidebarOpen)
React 19
Server Components (Default in Next.js App Router)
// Server Component β runs on server, zero JS to client
async function ProductList() {
const products = await db.products.findMany() // Direct DB access
return {products.map(p => )}
}// Client Component β needs 'use client' directive
'use client'
function AddToCartButton({ productId }: { productId: string }) {
const [loading, setLoading] = useState(false)
return
}
| Server Component | Client Component | |------------------|------------------| | async/await β | useState β | | Direct DB β | onClick β | | No bundle size | Adds to bundle | | useState β | async β |
use() Hook
// Read promises in render (with Suspense)
function Comments({ promise }: { promise: Promise }) {
const comments = use(promise) // Suspends until resolved
return {comments.map(c => - {c.text}
)}
}
useActionState (Forms)
'use client'
async function submitAction(prev: State, formData: FormData) {
'use server'
// ... server logic
return { success: true }
}function Form() {
const [state, action, pending] = useActionState(submitAction, {})
return (
)
}
Performance
| Priority | Technique | Impact | |----------|-----------|--------| | P0 | Route-based code splitting | π΄ High | | P0 | Image optimization (next/image) | π΄ High | | P1 | Virtualize long lists (tanstack-virtual) | π‘ Medium | | P1 | Debounce expensive operations | π‘ Medium | | P2 | React.memo on expensive components | π’ Low-Med | | P2 | useMemo for expensive calculations | π’ Low-Med |
React Compiler (React 19+): Auto-memoizes. Remove manual memo/useMemo/useCallback.
Common Traps
Rendering Traps
// β Renders "0" when count is 0
{count && }// β
Explicit boolean
{count > 0 && }
// β Mutating state β React won't detect
array.push(item)
setArray(array)// β
New reference
setArray([...array, item])
// β New key every render β destroys component
// β
Stable key
Hooks Traps
// β useEffect cannot be async
useEffect(async () => { ... }, [])// β
Define async inside
useEffect(() => {
async function load() { ... }
load()
}, [])
// β Missing cleanup β memory leak
useEffect(() => {
const sub = subscribe()
}, [])// β
Return cleanup
useEffect(() => {
const sub = subscribe()
return () => sub.unsubscribe()
}, [])
// β Object in deps β triggers every render
useEffect(() => { ... }, [{ id: 1 }])// β
Extract primitives or memoize
useEffect(() => { ... }, [id])
Data Fetching Traps
// β Sequential fetches β slow
const users = await fetchUsers()
const orders = await fetchOrders()// β
Parallel
const [users, orders] = await Promise.all([fetchUsers(), fetchOrders()])
// β Race condition β no abort
useEffect(() => {
fetch(url).then(setData)
}, [url])// β
Abort controller
useEffect(() => {
const controller = new AbortController()
fetch(url, { signal: controller.signal }).then(setData)
return () => controller.abort()
}, [url])
AI Mistakes to Avoid
Common errors AI assistants make with React:
| Mistake | Correct Pattern |
|---------|-----------------|
| useEffect for derived state | Compute inline: const x = a + b |
| Redux for API data | TanStack Query for server state |
| Default exports | Named exports: export function X |
| Index as key in dynamic lists | Stable IDs: key={item.id} |
| Fetching in useEffect | TanStack Query or loader patterns |
| Giant components (500+ lines) | Split at 50 lines JSX, 300 lines file |
| No error boundaries | Add at app, feature, component level |
| Ignoring TypeScript strict | Enable strict: true, fix all errors |
Quick Reference
Hooks
| Hook | Purpose | |------|---------| | useState | Local state | | useEffect | Side effects (subscriptions, DOM) | | useCallback | Stable function reference | | useMemo | Expensive calculation | | useRef | Mutable ref, DOM access | | use() | Read promise/context (React 19) | | useActionState | Form action state (React 19) | | useOptimistic | Optimistic UI (React 19) |
File Structure
src/
βββ app/ # Routes (Next.js)
βββ features/ # Feature modules
β βββ auth/
β βββ components/ # Feature components
β βββ hooks/ # Feature hooks
β βββ api/ # API calls
β βββ index.ts # Public exports
βββ shared/ # Cross-feature
β βββ components/ui/ # Button, Input, etc.
β βββ hooks/ # useDebounce, etc.
βββ providers/ # Context providers
Setup
See setup.md for first-time configuration. Uses memory-template.md for project tracking.
Core Rules
1. Server state β client state β API data goes in TanStack Query, UI state in useState/Zustand. Never mix.
2. Named exports only β export function X not export default. Enables safe refactoring.
3. Colocate, then extract β Start with state near usage. Lift only when needed.
4. No useEffect for derived state β Compute inline: const total = items.reduce(...). Effects are for side effects.
5. Stable keys always β Use item.id, never index for dynamic lists.
6. Max 50 lines JSX β If bigger, extract components. Max 300 lines per file.
7. TypeScript strict: true β No any, no implicit nulls. Catch bugs at compile time.
Related Skills
Install withclawhub install if user confirms:Feedback
clawhub star reactclawhub syncβ‘ When to Use
βοΈ Configuration
See setup.md for first-time configuration. Uses memory-template.md for project tracking.