Agent skill
react
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.
Install this agent skill to your Project
npx add-skill https://github.com/azlekov/my-claude-code/tree/main/skills/react
SKILL.md
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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
stripe
This skill should be used when the user asks to "integrate Stripe", "add payments", "create subscriptions", "handle webhooks", "usage-based billing", "per-seat pricing", "tiered plans", "checkout session", "customer portal", "sync Stripe data", "Stripe Sync Engine", "payment processing", "MRR analytics", "revenue reporting", or mentions 'Stripe', 'subscription', 'billing', 'webhook', 'checkout', 'metered billing', 'payment intent', 'stripe schema'. Automatically triggers for payment, subscription, and billing analytics work.
shadcn
This skill should be used when the user asks to "add a component", "use shadcn", "install Button", "create Dialog", "add Form", "use DataTable", "implement dark mode toggle", "use cn utility", or discusses UI components, component libraries, or accessible components. Always use the latest shadcn/ui version and modern patterns.
tailwindcss
This skill should be used when the user asks to "style with Tailwind", "add CSS", "configure theme", "use @theme", "add custom colors", "implement dark mode", "use container queries", "add responsive design", "use OKLCH colors", or discusses styling, theming, or visual design. Always use the latest Tailwind CSS version and modern patterns.
nextjs
This skill should be used when the user asks to "create a Next.js app", "build a page", "add routing", "implement server components", "add caching", "create API routes", "use server actions", "add metadata", "set up layouts", or discusses Next.js architecture, App Router, data fetching, or rendering strategies. Always use the latest Next.js version and modern patterns.
supabase-expert
This skill should be used when the user asks to "create a Supabase table", "write RLS policies", "set up Supabase Auth", "create Edge Functions", "configure Storage buckets", "use Supabase with Next.js", "migrate API keys", "implement row-level security", "create database functions", "set up SSR auth", or mentions 'Supabase', 'RLS', 'Edge Function', 'Storage bucket', 'anon key', 'service role', 'publishable key', 'secret key'. Automatically triggers when user mentions 'database', 'table', 'SQL', 'migration', 'policy'.
postgres-nanoid
This skill should be used when the user asks to "generate IDs", "create identifiers", "use nanoid", "add public_id", "prefixed identifiers", "short IDs", or discusses ID generation strategies, public vs internal IDs, or URL-friendly identifiers. Use nanoid for public identifiers and UUID for auth.users references.
Didn't find tool you were looking for?