| name | react |
| description | This skill should be used when the user asks to "create a React component", "use React hooks", "handle state", "implement forms", "use useOptimistic", "use useActionState", "create Server Components", "add interactivity", or discusses React patterns, component architecture, or state management. Always use the latest React version and modern patterns. |
| version | 1.0.0 |
React Development
This skill provides guidance for building applications with React, focusing on always using the latest version and modern patterns.
Philosophy: Prefer Server Components by default. Use modern hooks (useActionState, useOptimistic). Leverage the React Compiler for automatic optimization.
Quick Reference
| Feature | Modern Approach | Legacy (Avoid) |
|---|---|---|
| Form State | useActionState |
useFormState (deprecated) |
| Optimistic UI | useOptimistic |
Manual state management |
| Promises in Render | use() hook |
useEffect + useState |
| Context | use(Context) |
useContext(Context) |
| Memoization | React Compiler | Manual useMemo, useCallback |
| Refs | ref prop on functions |
forwardRef wrapper |
Component Types
Server Components (Default in App Router)
// No directive needed - server by default
async function UserProfile({ userId }: { userId: string }) {
const user = await db.users.find(userId)
return (
<div className="profile">
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
Server Components can:
- Use
async/await - Access databases directly
- Read files from filesystem
- Keep secrets server-side
Server Components cannot:
- Use
useState,useEffect - Add event handlers
- Access browser APIs
Client Components
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
)
}
Use Client Components when you need:
- Interactivity (onClick, onChange)
- Browser APIs (localStorage, window)
- React hooks (useState, useEffect)
Modern Hooks
useActionState
Handle form actions with loading and error states:
'use client'
import { useActionState } from 'react'
interface FormState {
error: string | null
success: boolean
}
async function submitForm(prevState: FormState, formData: FormData): Promise<FormState> {
const email = formData.get('email') as string
if (!email.includes('@')) {
return { error: 'Invalid email', success: false }
}
await saveEmail(email)
return { error: null, success: true }
}
export function EmailForm() {
const [state, formAction, isPending] = useActionState(submitForm, {
error: null,
success: false
})
return (
<form action={formAction}>
<input
name="email"
type="email"
disabled={isPending}
placeholder="Enter email"
/>
<button type="submit" disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</button>
{state.error && <p className="text-red-500">{state.error}</p>}
{state.success && <p className="text-green-500">Success!</p>}
</form>
)
}
useOptimistic
Provide instant UI feedback while async operations complete:
'use client'
import { useOptimistic, useTransition } from 'react'
interface Message {
id: string
text: string
sending?: boolean
}
export function MessageList({
messages,
sendMessage
}: {
messages: Message[]
sendMessage: (text: string) => Promise<void>
}) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage: Message) => [...state, { ...newMessage, sending: true }]
)
const [, startTransition] = useTransition()
async function handleSubmit(formData: FormData) {
const text = formData.get('text') as string
// Instantly show the message
addOptimisticMessage({ id: `temp-${Date.now()}`, text })
// Then actually send it
startTransition(async () => {
await sendMessage(text)
})
}
return (
<div>
<ul>
{optimisticMessages.map(msg => (
<li
key={msg.id}
className={msg.sending ? 'opacity-50' : ''}
>
{msg.text}
{msg.sending && <span className="ml-2">Sending...</span>}
</li>
))}
</ul>
<form action={handleSubmit}>
<input name="text" placeholder="Type a message" />
<button type="submit">Send</button>
</form>
</div>
)
}
use() Hook
Read promises and context directly in render:
import { use, Suspense } from 'react'
// Reading a promise
function UserName({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise)
return <h1>{user.name}</h1>
}
export function UserProfile({ userId }: { userId: string }) {
const userPromise = fetchUser(userId) // Start fetching
return (
<Suspense fallback={<div>Loading...</div>}>
<UserName userPromise={userPromise} />
</Suspense>
)
}
// Reading context (replaces useContext)
import { ThemeContext } from './theme'
function ThemedButton() {
const theme = use(ThemeContext)
return <button className={theme.buttonClass}>Click me</button>
}
React Compiler
Enable automatic memoization without manual useMemo/useCallback:
// next.config.ts
const nextConfig = {
experimental: {
reactCompiler: true
}
}
Before (manual memoization):
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
const processedData = useMemo(() => expensiveProcess(data), [data])
const handleClick = useCallback(() => doSomething(data), [data])
return <div onClick={handleClick}>{processedData}</div>
})
After (React Compiler handles it):
function ExpensiveComponent({ data }) {
const processedData = expensiveProcess(data)
const handleClick = () => doSomething(data)
return <div onClick={handleClick}>{processedData}</div>
}
Component Patterns
Composition Over Props
// Instead of prop drilling
function Card({ title, subtitle, children, footer }) {
return (
<div className="card">
<h2>{title}</h2>
<p>{subtitle}</p>
{children}
<div>{footer}</div>
</div>
)
}
// Use composition
function Card({ children }) {
return <div className="card">{children}</div>
}
Card.Header = function Header({ children }) {
return <div className="card-header">{children}</div>
}
Card.Body = function Body({ children }) {
return <div className="card-body">{children}</div>
}
Card.Footer = function Footer({ children }) {
return <div className="card-footer">{children}</div>
}
// Usage
<Card>
<Card.Header>
<h2>Title</h2>
</Card.Header>
<Card.Body>Content here</Card.Body>
<Card.Footer>
<button>Action</button>
</Card.Footer>
</Card>
Render Props
interface MousePosition {
x: number
y: number
}
function MouseTracker({
children
}: {
children: (position: MousePosition) => React.ReactNode
}) {
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: {x}, {y}</div>}
</MouseTracker>
Server/Client Boundary
// Server Component (fetches data)
async function Dashboard() {
const stats = await fetchStats()
const activities = await fetchActivities()
return (
<div>
<StatsCards stats={stats} /> {/* Server */}
<InteractiveChart data={stats} /> {/* Client */}
<ActivityFeed activities={activities} /> {/* Server */}
<FilterPanel /> {/* Client */}
</div>
)
}
// Client Component (interactive)
'use client'
function InteractiveChart({ data }) {
const [range, setRange] = useState('7d')
// Chart logic...
}
Refs in React 19
Direct Ref Prop (No More forwardRef)
// React 19: Direct ref prop
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />
}
// Usage
function Form() {
const inputRef = useRef<HTMLInputElement>(null)
return <Input ref={inputRef} placeholder="Name" />
}
Cleanup Functions
function Component() {
const ref = useCallback((node: HTMLDivElement | null) => {
if (node) {
// Setup
const observer = new IntersectionObserver(...)
observer.observe(node)
// Cleanup (new in React 19)
return () => observer.disconnect()
}
}, [])
return <div ref={ref}>Observed</div>
}
Context
Creating Context
import { createContext, use } from 'react'
interface User {
id: string
name: string
}
const UserContext = createContext<User | null>(null)
function UserProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
return (
<UserContext value={user}>
{children}
</UserContext>
)
}
Consuming Context (Modern)
function UserProfile() {
const user = use(UserContext)
if (!user) return <div>Please log in</div>
return <div>Hello, {user.name}</div>
}
Error Boundaries
'use client'
import { Component, type ReactNode } from 'react'
interface Props {
children: ReactNode
fallback: ReactNode
}
interface State {
hasError: boolean
}
class ErrorBoundary extends Component<Props, State> {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
render() {
if (this.state.hasError) {
return this.props.fallback
}
return this.props.children
}
}
// Usage
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<RiskyComponent />
</ErrorBoundary>
Additional Resources
For detailed patterns, see reference files:
references/hooks.md- Complete hooks referencereferences/server-components.md- RSC patterns and best practices