---
name: react-patterns
description: Use when implementing React 19 features, optimizing components, or choosing between Actions vs TanStack Query for mutations. Covers React Compiler optimization, Server Actions, Forms, and new hooks.
updated: 2026-01-20
keywords: react-19, react-compiler, server-actions, forms, hooks, use-hook, optimistic-updates
---
# React 19 Patterns and Best Practices
Modern React 19 patterns leveraging the React Compiler, Server Actions, and new hooks.
## Compiler-Friendly Code
The React Compiler automatically optimizes components for performance. Write code that works well with it:
**Best Practices:**
- Keep components pure and props serializable
- Derive values during render (don't stash in refs unnecessarily)
- Keep event handlers inline unless they close over large mutable objects
- Verify compiler is working (DevTools ✨ badge)
- Opt-out problematic components with `"use no memo"` while refactoring
**Example - Pure Component:**
```typescript
// ✅ Compiler-friendly - pure function
function UserCard({ user }: { user: User }) {
const displayName = `${user.firstName} ${user.lastName}`
const isVIP = user.points > 1000
return (
{displayName}
{isVIP && VIP}
)
}
// ❌ Avoid - unnecessary effects
function UserCard({ user }: { user: User }) {
const [displayName, setDisplayName] = useState('')
useEffect(() => {
setDisplayName(`${user.firstName} ${user.lastName}`)
}, [user])
return
{displayName}
}
```
**Verification:**
- Open React DevTools
- Look for "Memo ✨" badge on components
- If missing, component wasn't optimized (check for violations)
**Opt-Out When Needed:**
```typescript
'use no memo'
// Component code that can't be optimized yet
function ProblematicComponent() {
// ... code with compiler issues
}
```
## Actions & Forms
For SPA mutations, choose **one approach per feature**:
- **React 19 Actions:** `
)
}
```
**With State (useActionState):**
```typescript
import { useActionState } from 'react'
function TodoForm() {
const [state, formAction, isPending] = useActionState(
createTodoAction,
{ error: null, success: false }
)
return (
)
}
```
**With Optimistic Updates (useOptimistic):**
```typescript
import { useOptimistic } from 'react'
function TaskList({ initialTodos }: { initialTodos: Todo[] }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
initialTodos,
(state, newTodo: string) => [
...state,
{ id: `temp-${Date.now()}`, text: newTodo, completed: false }
]
)
async function handleSubmit(formData: FormData) {
const text = formData.get('text') as string
addOptimisticTodo(text)
await createTodoAction(formData)
}
return (
<>
{optimisticTodos.map(todo => (
{todo.text}
))}
>
)
}
```
### TanStack Query Mutations (Preferred for SPAs)
**Best for:**
- Non-form mutations (e.g., button clicks)
- Complex optimistic updates with rollback
- Integration with existing Query cache
- More control over caching and invalidation
See **tanstack-query** skill for comprehensive mutation patterns.
**Quick Example:**
```typescript
import { useMutation, useQueryClient } from '@tanstack/react-query'
function useCre
ateTodo() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (text: string) => api.post('/todos', { text }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
}
// Usage
function TodoForm() {
const createTodo = useCreateTodo()
return (
)
}
```
## The `use` Hook
The `use` hook unwraps Promises and Context, enabling new patterns.
**With Promises:**
```typescript
import { use, Suspense } from 'react'
function UserProfile({ userPromise }: { userPromise: Promise }) {
const user = use(userPromise)
return
{user.name}
}
// Usage
function App() {
const userPromise = fetchUser(1)
return (
}>
)
}
```
**With Context:**
```typescript
import { use, createContext } from 'react'
const ThemeContext = createContext('light')
function Button() {
const theme = use(ThemeContext)
return
}
```
**When to Use:**
- Primarily useful with Suspense/data primitives and RSC (React Server Components)
- **For SPA-only apps**, prefer **TanStack Query + Router loaders** for data fetching
- `use` shines when you already have a Promise from a parent component
## Component Composition Patterns
**Compound Components:**
```typescript
// ✅ Good - composable, flexible
Dashboard
{/* content */}
// Implementation
function Card({ children }: { children: React.ReactNode }) {
return
{children}
}
Card.Header = function CardHeader({ children }: { children: React.ReactNode }) {
return {children}
}
Card.Title = function CardTitle({ children }: { children: React.ReactNode }) {
return
{children}
}
Card.Content = function CardContent({ children }: { children: React.ReactNode }) {
return
{children}
}
```
**Render Props (when needed):**
```typescript
function DataLoader({
fetch,
render
}: {
fetch: () => Promise
render: (data: T) => React.ReactNode
}) {
const { data } = useQuery({ queryKey: ['data'], queryFn: fetch })
if (!data) return
return <>{render(data)}>
}
// Usage
fetchUser(1)}
render={(user) => }
/>
```
## Error Boundaries
React 19 still requires class components for error boundaries (or use a library):
```typescript
import { Component, ReactNode } from 'react'
class ErrorBoundary extends Component<
{ children: ReactNode; fallback: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(error: Error, info: { componentStack: string }) {
console.error('Error caught:', error, info)
}
render() {
if (this.state.hasError) {
return this.props.fallback
}
return this.props.children
}
}
// Usage
}>
```
**Or use react-error-boundary library:**
```typescript
import { ErrorBoundary } from 'react-error-boundary'
Something went wrong}
onError={(error, info) => console.error(error, info)}
>
```
## Decision Guide: Actions vs Query Mutations
| Scenario | Recommendation |
|----------|---------------|
| Form submission with validation | React Actions |
| Button click mutation | TanStack Query |
| Needs optimistic updates + rollback | TanStack Query |
| Integrates with existing cache | TanStack Query |
| SSR/RSC application | React Actions |
| SPA with complex data flow | TanStack Query |
| Simple CRUD with forms | React Actions |
**Rule of Thumb:** For SPAs with TanStack Query already in use, prefer Query mutations for consistency. Only use Actions for form-heavy features where the form-centric API is beneficial.
## Related Skills
- **tanstack-query** - Server state with mutations and optimistic updates
- **core-principles** - Overall project structure
- **tooling-setup** - React Compiler configuration