//
Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
These skill recipes are designed for Claude Code but also work with other AI coding agents that support skill/instruction files.
The complete SKILL.md content you can copy into .claude/skills/react-19-mastery/SKILL.md:
---
name: react-19-mastery
description: "Expert guidance on React 19 features, compiler, actions, and new hooks. Use when asked to: React 19 help, new hooks, server components, React compiler, useActionState, useOptimistic, use() hook, migrate from React 18."
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Agent"
---
# React 19 Mastery
You are a React 19 expert. When this skill is invoked, provide authoritative guidance on React 19 features, migration, and best practices.
## Core React 19 Features Reference
### New Hooks
1. **useActionState(action, initialState, permalink?)** - Manages form/action state with pending tracking
- Returns [state, formAction, isPending]
- Replaces useFormState (renamed in React 19 stable)
- Use for: any form submission, server action integration, progressive enhancement
- The action receives previousState as its first argument
2. **useOptimistic(state, updateFn)** - Instant UI feedback during async operations
- Returns [optimisticState, addOptimistic]
- Automatically reverts when the parent rerender completes
- Use for: likes, bookmarks, toggles, any mutation where you can predict the result
3. **useFormStatus()** - Read parent form submission status from a child component
- Returns { pending, data, method, action }
- MUST be called from a component rendered inside a <form>
- Use for: submit button disabled states, loading spinners inside forms
4. **use(resource)** - Unwrap promises and context conditionally
- Works with Promises (Suspense-aware) and Context
- CAN be called inside conditionals and loops (unlike other hooks)
- Use for: reading async data in render, conditional context reads
### React Compiler
- Automatically memoizes components, hooks, and expressions
- Eliminates most manual useMemo, useCallback, and React.memo usage
- Rules of React must be followed strictly (no mutations during render, stable hook call order)
- Install: babel-plugin-react-compiler or the Vite/Next.js plugin
- Validate with eslint-plugin-react-compiler before enabling
### Server Components (RSC)
- Default in App Router (no "use client" directive = Server Component)
- Can directly access databases, file systems, and server-only APIs
- Cannot use hooks, event handlers, browser APIs, or state
- Props passed to Client Components must be serializable
### Server Actions
- Async functions marked with "use server" at the top of the function body or file
- Can be passed to forms as action={serverAction}
- Can be called from Client Components like regular async functions
- Always validate inputs server-side even if client validation exists
### ref as prop
- Function components now accept ref as a regular prop
- forwardRef is no longer needed (still works but deprecated)
- Pattern: function Input({ ref, ...props }) { return <input ref={ref} {...props} /> }
### Document Metadata Hoisting
- <title>, <meta>, and <link> tags in components auto-hoist to <head>
- Works in both Server and Client Components
- Eliminates need for next/head or react-helmet in many cases
## Decision Rules
When a user asks about React 19, follow these rules:
### When to recommend useActionState
- User is building a form that submits data
- User needs to track pending state during submission
- User wants progressive enhancement (works without JS)
- User is migrating from useFormState
### When to recommend useOptimistic
- User wants instant feedback on a mutation
- The optimistic result is predictable from the input
- User is building like/bookmark/toggle features
- ALWAYS pair with error handling to revert on failure
### When to recommend use()
- User needs to read a promise in a component (wrap parent in Suspense)
- User needs conditional context access
- Do NOT recommend for initial data fetching in Next.js (use async Server Components instead)
### When to recommend React Compiler
- User is on React 19 and has a clean codebase following Rules of React
- User has performance issues from missing memoization
- ALWAYS recommend running eslint-plugin-react-compiler first
- Do NOT recommend if codebase has mutation patterns or side effects in render
## Migration Patterns from React 18
### useFormState to useActionState
```tsx
// React 18 (react-dom)
import \{ useFormState \} from "react-dom";
const [state, formAction] = useFormState(action, initial);
// React 19 (react)
import \{ useActionState \} from "react";
const [state, formAction, isPending] = useActionState(action, initial);
// Note: isPending is now built-in, no need for useFormStatus just for pending// React 18
const Input = forwardRef<HTMLInputElement, Props>((props, ref) => (
<input ref=\{ref\} \{...props\} />
));
// React 19
function Input(\{ ref, ...props \}: Props & \{ ref?: React.Ref<HTMLInputElement> \}) \{
return <input ref=\{ref\} \{...props\} />;
\}// React 18 - manual memoization
const MemoComponent = React.memo((\{ data \}: Props) => \{
const processed = useMemo(() => expensive(data), [data]);
const handler = useCallback(() => doThing(data), [data]);
return <Child data=\{processed\} onClick=\{handler\} />;
\});
// React 19 with Compiler - just write plain code
function Component(\{ data \}: Props) \{
const processed = expensive(data);
const handler = () => doThing(data);
return <Child data=\{processed\} onClick=\{handler\} />;
\}form element, not from useActionState
## Working Example
### Example 1: User asks "Help me build a form with React 19"
**User prompt:** "I need to build a comment form that shows optimistic updates"
**Skill-guided response would include:**
```tsx
// actions.ts
"use server";
import { z } from "zod";
const CommentSchema = z.object({
text: z.string().min(1).max(500),
});
export async function addComment(prevState: any, formData: FormData) {
const parsed = CommentSchema.safeParse({
text: formData.get("text"),
});
if (!parsed.success) {
return { error: parsed.error.flatten().fieldErrors };
}
await db.comment.create({ data: { text: parsed.data.text } });
return { error: null };
}
// CommentForm.tsx
"use client";
import { useActionState, useOptimistic } from "react";
import { addComment } from "./actions";
import { SubmitButton } from "./SubmitButton";
type Comment = { id: string; text: string };
export function CommentForm({ comments }: { comments: Comment[] }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(state, newText: string) => [
...state,
{ id: crypto.randomUUID(), text: newText },
]
);
const [state, formAction, isPending] = useActionState(
async (prev: any, formData: FormData) => {
const text = formData.get("text") as string;
addOptimisticComment(text);
return addComment(prev, formData);
},
{ error: null }
);
return (
<>
<ul>
{optimisticComments.map((c) => (
<li key={c.id}>{c.text}</li>
))}
</ul>
<form action={formAction}>
<input name="text" required />
{state.error && <p className="text-red-500">{state.error.text}</p>}
<SubmitButton />
</form>
</>
);
}// SubmitButton.tsx
"use client";
import { useFormStatus } from "react-dom";
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Posting..." : "Post Comment"}
</button>
);
}Skill-guided response would include:
npx eslint-plugin-react-compiler on the codebase firstThis skill turns Claude into a React 19 specialist by providing:
You can extend this skill by:
useServerAction wrapper")Create the file .claude/skills/react-19-mastery/SKILL.md in your project root and paste the complete content from the Recipe section above.
mkdir -p .claude/skills/react-19-mastery
# Then paste the Recipe content into .claude/skills/react-19-mastery/SKILL.md| Approach | When to Use |
|---|---|
| React 18 + manual optimization | Legacy projects not ready to migrate |
| Preact Signals | Smaller bundle, different reactivity model |
| Solid.js | Fine-grained reactivity without virtual DOM |
| Svelte 5 | Compiler-first approach with runes |
useActionState(action, initialState) -- manages form/action state with pending trackinguseOptimistic(state, updateFn) -- instant UI feedback during async operationsuseFormStatus() -- reads parent form submission status from a child componentuse(resource) -- unwraps promises and context, can be called inside conditionals// React 18 (from react-dom)
const [state, formAction] = useFormState(action, initial);
// React 19 (from react)
const [state, formAction, isPending] = useActionState(action, initial);useFormState to useActionStatereact-dom to reactisPending as a third value (no need for separate useFormStatus just for pending)useMemo, useCallback, and React.memo usageeslint-plugin-react-compiler to check for violations// React 19 -- ref is a regular prop
function Input({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}forwardRef is no longer needed (still works but is deprecated)ref directly as a prop<form> elementuseFormStatus inside a <form>, not in the same component that creates the form"use client" directive means Server Component)type FormState = { error: string | null; success: boolean };
const [state, formAction, isPending] = useActionState(
async (prev: FormState, formData: FormData): Promise<FormState> => {
// ...
return { error: null, success: true };
},
{ error: null, success: false }
);previousState as its first argument and FormData as seconduse() unwraps resources (Promises, Context) during render, not as a side effect<Suspense> boundary above when used with Promises// React 18 -- manual memoization
const MemoComponent = React.memo(({ data }: Props) => {
const processed = useMemo(() => expensive(data), [data]);
return <Child data={processed} />;
});
// React 19 with Compiler -- plain code
function Component({ data }: Props) {
const processed = expensive(data);
return <Child data={processed} />;
}React.memo, useMemo, and useCallback wrappers<title>, <meta>, and <link> tags in components auto-hoist to <head>next/head or react-helmet in many casesReviewed by Chris St. John·Last updated Jul 7, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥