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.
Provides guidance for building React applications using the latest version and modern patterns. Triggers when users request React components, hooks, state management, forms, or discuss React architecture.
/plugin marketplace add azlekov/my-claude-code/plugin install personal@my-claude-codeThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/hooks.mdreferences/server-components.mdThis 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.
| 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 |
// 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:
async/awaitServer Components cannot:
useState, useEffect'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:
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>
)
}
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>
)
}
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>
}
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>
}
// 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>
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 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...
}
// 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" />
}
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>
}
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>
)
}
function UserProfile() {
const user = use(UserContext)
if (!user) return <div>Please log in</div>
return <div>Hello, {user.name}</div>
}
'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>
For detailed patterns, see reference files:
references/hooks.md - Complete hooks referencereferences/server-components.md - RSC patterns and best practicesThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.