| name | react-patterns |
| version | 2.0.0 |
| description | Comprehensive React 19 patterns expert covering Server Components, Actions, use() hook, useOptimistic, useFormStatus, useFormState, React Compiler, concurrent features, Suspense, and modern TypeScript development.
Triggers: "React", "components", "hooks", "JSX/TSX", "state management"
Anti-triggers: "Vue", "Svelte", "Angular", "backend"
|
| user-invocable | false |
| language | typescript,javascript,tsx,jsx |
| framework | react |
| license | MIT |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
| tags | ["react","react-19","typescript","javascript","jsx","tsx","hooks","server-components","actions","suspense","concurrent-rendering","react-compiler","use-optimistic","form-actions"] |
| category | frontend |
| subcategories | ["components","hooks","state-management","performance","server-components","forms","testing"] |
React Development Patterns
Expert guide for building modern React 19 applications with new concurrent features, Server Components, Actions, and advanced patterns.
When to Use
- Building React 19 components with TypeScript/JavaScript
- Managing component state with useState and useReducer
- Handling side effects with useEffect
- Optimizing performance with useMemo and useCallback
- Creating custom hooks for reusable logic
- Implementing component composition patterns
- Working with refs using useRef
- Using React 19's new features (use(), useOptimistic, useFormStatus)
- Implementing Server Components and Actions
- Working with Suspense and concurrent rendering
- Building forms with new form hooks
Core Hooks Patterns
useState - State Management
Basic state declaration and updates:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
State with initializer function (expensive computation):
const [state, setState] = useState(() => {
const initialState = computeExpensiveValue();
return initialState;
});
Multiple state variables:
function UserProfile() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');
return (
<form>
<input value={name} onChange={e => setName(e.target.value)} />
<input type="number" value={age} onChange={e => setAge(Number(e.target.value))} />
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
</form>
);
}
useEffect - Side Effects
Basic effect with cleanup:
import { useEffect } from 'react';
function ChatRoom({ roomId }: { roomId: string }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
return <div>Connected to {roomId}</div>;
}
Effect with multiple dependencies:
function ChatRoom({ roomId, serverUrl }: { roomId: string; serverUrl: string }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
return <h1>Welcome to {roomId}</h1>;
}
Effect for subscriptions:
function StatusBar() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
useRef - Persistent References
Storing mutable values without re-renders:
import { useRef } from 'react';
function Timer() {
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const startTimer = () => {
intervalRef.current = setInterval(() => {
console.log('Tick');
}, 1000);
};
const stopTimer = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
return (
<>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</>
);
}
DOM element references:
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</>
);
}
Custom Hooks Pattern
Extract reusable logic into custom hooks:
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
function StatusBar() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
function SaveButton() {
const isOnline = useOnlineStatus();
return (
<button disabled={!isOnline}>
{isOnline ? 'Save' : 'Reconnecting...'}
</button>
);
}
Custom hook with parameters:
import { useEffect } from 'react';
interface ChatOptions {
serverUrl: string;
roomId: string;
}
export function useChatRoom({ serverUrl, roomId }: ChatOptions) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]);
}
function ChatRoom({ roomId }: { roomId: string }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useChatRoom({ serverUrl, roomId });
return (
<>
<input value={serverUrl} onChange={e => setServerUrl(e.target.value)} />
<h1>Welcome to {roomId}</h1>
</>
);
}
Component Composition Patterns
Props and Children
Basic component with props:
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
onClick?: () => void;
children: React.ReactNode;
}
function Button({ variant = 'primary', size = 'md', onClick, children }: ButtonProps) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
onClick={onClick}
>
{children}
</button>
);
}
Composition with children:
interface CardProps {
children: React.ReactNode;
className?: string;
}
function Card({ children, className = '' }: CardProps) {
return (
<div className={`card ${className}`}>
{children}
</div>
);
}
function UserProfile() {
return (
<Card>
<h2>John Doe</h2>
<p>Software Engineer</p>
</Card>
);
}
Lifting State Up
Shared state between siblings:
function Parent() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
Panel 1 content
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
Panel 2 content
</Panel>
</>
);
}
interface PanelProps {
isActive: boolean;
onShow: () => void;
children: React.ReactNode;
}
function Panel({ isActive, onShow, children }: PanelProps) {
return (
<div>
<button onClick={onShow}>Show</button>
{isActive && <div>{children}</div>}
</div>
);
}
Performance Optimization
Avoid Unnecessary Effects
❌ Bad - Using effect for derived state:
function TodoList({ todos }: { todos: Todo[] }) {
const [visibleTodos, setVisibleTodos] = useState<Todo[]>([]);
useEffect(() => {
setVisibleTodos(todos.filter(t => !t.completed));
}, [todos]);
return <ul>{/* ... */}</ul>;
}
✅ Good - Compute during render:
function TodoList({ todos }: { todos: Todo[] }) {
const visibleTodos = todos.filter(t => !t.completed);
return <ul>{/* ... */}</ul>;
}
useMemo for Expensive Computations
import { useMemo } from 'react';
function DataTable({ data }: { data: Item[] }) {
const sortedData = useMemo(() => {
return [...data].sort((a, b) => a.name.localeCompare(b.name));
}, [data]);
return <table>{/* render sortedData */}</table>;
}
useCallback for Function Stability
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Clicked', count);
}, [count]);
return <ExpensiveChild onClick={handleClick} />;
}
TypeScript Best Practices
Type-Safe Props
interface UserProps {
id: string;
name: string;
email: string;
age?: number;
}
function User({ id, name, email, age }: UserProps) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
{age && <p>Age: {age}</p>}
</div>
);
}
Generic Components
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
/>
Event Handlers
function Form() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
</form>
);
}
Common Patterns
Controlled Components
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={e => setValue(e.target.value)}
/>
);
}
Conditional Rendering
function Greeting({ isLoggedIn }: { isLoggedIn: boolean }) {
return (
<div>
{isLoggedIn ? (
<UserGreeting />
) : (
<GuestGreeting />
)}
</div>
);
}
Lists and Keys
function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
);
}
Best Practices
General React Best Practices
- Dependency Arrays: Always specify correct dependencies in useEffect
- State Structure: Keep state minimal and avoid redundant state
- Component Size: Keep components small and focused
- Custom Hooks: Extract complex logic into reusable custom hooks
- TypeScript: Use TypeScript for type safety
- Keys: Use stable IDs as keys for list items, not array indices
- Immutability: Never mutate state directly
- Effects: Use effects only for synchronization with external systems
- Performance: Profile before optimizing with useMemo/useCallback
React 19 Specific Best Practices
- Server Components: Use Server Components for data fetching and static content
- Client Components: Mark components as 'use client' only when necessary
- Actions: Use Server Actions for mutations and form submissions
- Optimistic Updates: Implement useOptimistic for better UX
- use() Hook: Use for reading promises and context conditionally
- Form State: Use useFormState and useFormStatus for complex forms
- Concurrent Features: Leverage useTransition for non-urgent updates
- Error Boundaries: Implement proper error handling with error boundaries
Common Pitfalls
General React Pitfalls
❌ Missing Dependencies:
useEffect(() => {
console.log(count);
}, []);
❌ Mutating State:
const [items, setItems] = useState([]);
items.push(newItem);
setItems(items);
✅ Correct Approach:
setItems([...items, newItem]);
React 19 Specific Pitfalls
❌ Using use() outside of render:
function handleClick() {
const data = use(promise);
}
✅ Correct usage:
function Component({ promise }) {
const data = use(promise);
return <div>{data}</div>;
}
❌ Forgetting 'use server' directive:
export async function myAction() {
}
✅ Correct Server Action:
'use server';
export async function myAction() {
}
❌ Mixing Server and Client logic incorrectly:
export default async function ServerComponent() {
const width = window.innerWidth;
return <div>{width}</div>;
}
✅ Correct separation:
export default async function ServerComponent() {
const data = await fetchData();
return <ClientComponent data={data} />;
}
'use client';
function ClientComponent({ data }) {
const [width, setWidth] = useState(window.innerWidth);
return <div>{width}</div>;
}
React 19 New Features
use() Hook - Reading Resources
The use() hook reads the value from a resource like a Promise or Context:
import { use } from 'react';
function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
return <p>{message}</p>;
}
function Button() {
if (condition) {
const theme = use(ThemeContext);
return <button className={theme}>Click</button>;
}
return <button>Click</button>;
}
useOptimistic Hook - Optimistic UI Updates
Manage optimistic UI updates for async operations:
import { useOptimistic } from 'react';
function TodoList({ todos, addTodo }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, newTodo]
);
const handleSubmit = async (formData) => {
const newTodo = { id: Date.now(), text: formData.get('text') };
addOptimisticTodo(newTodo);
await addTodo(newTodo);
};
return (
<form action={handleSubmit}>
{optimisticTodos.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
<input type="text" name="text" />
<button type="submit">Add Todo</button>
</form>
);
}
useFormStatus Hook - Form State
Access form submission status from child components:
import { useFormStatus } from 'react';
function SubmitButton() {
const { pending, data } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
function ContactForm() {
return (
<form action={submitForm}>
<input name="email" type="email" />
<SubmitButton />
</form>
);
}
useFormState Hook - Form State Management
Manage form state with error handling:
import { useFormState } from 'react';
async function submitAction(prevState: string | null, formData: FormData) {
const email = formData.get('email') as string;
if (!email.includes('@')) {
return 'Invalid email address';
}
await submitToDatabase(email);
return null;
}
function EmailForm() {
const [state, formAction] = useFormState(submitAction, null);
return (
<form action={formAction}>
<input name="email" type="email" />
<button type="submit">Subscribe</button>
{state && <p className="error">{state}</p>}
</form>
);
}
Server Actions
Define server-side functions for form handling:
'use server';
import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
if (!title || !content) {
return { error: 'Title and content are required' };
}
const post = await db.post.create({
data: { title, content }
});
revalidatePath('/posts');
redirect(`/posts/${post.id}`);
}
Server Components
Components that run exclusively on the server:
async function PostsPage() {
const posts = await db.post.findMany({
orderBy: { createdAt: 'desc' },
take: 10
});
return (
<div>
<h1>Latest Posts</h1>
<PostsList posts={posts} />
</div>
);
}
'use client';
function PostsList({ posts }: { posts: Post[] }) {
const [selectedId, setSelectedId] = useState<number | null>(null);
return (
<ul>
{posts.map(post => (
<li
key={post.id}
onClick={() => setSelectedId(post.id)}
className={selectedId === post.id ? 'selected' : ''}
>
{post.title}
</li>
))}
</ul>
);
}
React Compiler
Automatic Optimization
React Compiler automatically optimizes your components:
const ExpensiveComponent = memo(function ExpensiveComponent({
data,
onUpdate
}) {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
computed: expensiveCalculation(item)
}));
}, [data]);
const handleClick = useCallback((id) => {
onUpdate(id);
}, [onUpdate]);
return (
<div>
{processedData.map(item => (
<Item
key={item.id}
item={item}
onClick={handleClick}
/>
))}
</div>
);
});
function ExpensiveComponent({ data, onUpdate }) {
const processedData = data.map(item => ({
...item,
computed: expensiveCalculation(item)
}));
const handleClick = (id) => {
onUpdate(id);
};
return (
<div>
{processedData.map(item => (
<Item
key={item.id}
item={item}
onClick={handleClick}
/>
))}
</div>
);
}
Installation and Setup
npm install -D babel-plugin-react-compiler@latest
npm install -D eslint-plugin-react-hooks@latest
module.exports = {
plugins: [
'babel-plugin-react-compiler',
],
};
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
});
Compiler Configuration
module.exports = {
plugins: [
[
'babel-plugin-react-compiler',
{
target: '18',
debug: process.env.NODE_ENV === 'development'
}
],
],
};
module.exports = {
plugins: [],
overrides: [
{
test: './src/components/**/*.{js,jsx,ts,tsx}',
plugins: ['babel-plugin-react-compiler']
}
]
};
Advanced Server Components Patterns
Mixed Server/Client Architecture
async function ProductPage({ id }: { id: string }) {
const product = await fetchProduct(id);
const related = await fetchRelatedProducts(id);
return (
<div>
<ProductDetails product={product} />
<ProductGallery images={product.images} />
<RelatedProducts products={related} />
</div>
);
}
'use client';
function ProductDetails({ product }: { product: Product }) {
const [quantity, setQuantity] = useState(1);
const [isAdded, setIsAdded] = useState(false);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
<QuantitySelector
value={quantity}
onChange={setQuantity}
/>
<AddToCartButton
productId={product.id}
quantity={quantity}
onAdded={() => setIsAdded(true)}
/>
{isAdded && <p>Added to cart!</p>}
</div>
);
}
Server Actions with Validation
'use server';
import { z } from 'zod';
const checkoutSchema = z.object({
items: z.array(z.object({
productId: z.string(),
quantity: z.number().min(1)
})),
shippingAddress: z.object({
street: z.string().min(1),
city: z.string().min(1),
zipCode: z.string().regex(/^\d{5}$/)
}),
paymentMethod: z.enum(['credit', 'paypal', 'apple'])
});
export async function processCheckout(
prevState: any,
formData: FormData
) {
const rawData = {
items: JSON.parse(formData.get('items') as string),
shippingAddress: {
street: formData.get('street'),
city: formData.get('city'),
zipCode: formData.get('zipCode')
},
paymentMethod: formData.get('paymentMethod')
};
const result = checkoutSchema.safeParse(rawData);
if (!result.success) {
return {
error: 'Validation failed',
fieldErrors: result.error.flatten().fieldErrors
};
}
try {
const order = await createOrder(result.data);
await updateInventory(result.data.items);
await sendConfirmationEmail(order);
revalidatePath('/orders');
return { success: true, orderId: order.id };
} catch (error) {
return { error: 'Payment failed' };
}
}
Concurrent Features
useTransition for Non-Urgent Updates
import { useTransition, useState } from 'react';
function SearchableList({ items }: { items: Item[] }) {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const [filteredItems, setFilteredItems] = useState(items);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
startTransition(() => {
setFilteredItems(
items.filter(item =>
item.name.toLowerCase().includes(e.target.value.toLowerCase())
)
);
});
};
return (
<div>
<input
type="text"
value={query}
onChange={handleChange}
placeholder="Search items..."
/>
{isPending && <div className="loading">Filtering...</div>}
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
useDeferredValue for Expensive UI
import { useDeferredValue, useMemo } from 'react';
function DataGrid({ data }: { data: DataRow[] }) {
const [searchTerm, setSearchTerm] = useState('');
const deferredSearchTerm = useDeferredValue(searchTerm);
const filteredData = useMemo(() => {
return data.filter(row =>
Object.values(row).some(value =>
String(value).toLowerCase().includes(deferredSearchTerm.toLowerCase())
)
);
}, [data, deferredSearchTerm]);
return (
<div>
<input
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
placeholder="Search..."
className={searchTerm !== deferredSearchTerm ? 'stale' : ''}
/>
<DataGridRows
data={filteredData}
isStale={searchTerm !== deferredSearchTerm}
/>
</div>
);
}
Testing React 19 Features
Testing Server Actions
import { render, screen, fireEvent } from '@testing-library/react';
import { jest } from '@jest/globals';
import ContactForm from './ContactForm';
const mockSubmitForm = jest.fn();
describe('ContactForm', () => {
it('submits form with server action', async () => {
render(<ContactForm />);
fireEvent.change(screen.getByLabelText('Email'), {
target: { value: 'test@example.com' }
});
fireEvent.click(screen.getByText('Submit'));
expect(mockSubmitForm).toHaveBeenCalledWith(
expect.any(FormData)
);
});
it('shows loading state during submission', async () => {
mockSubmitForm.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
render(<ContactForm />);
fireEvent.click(screen.getByText('Submit'));
expect(screen.getByText('Submitting...')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('Submit')).toBeInTheDocument();
});
});
});
Testing Optimistic Updates
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { jest } from '@jest/globals';
import TodoList from './TodoList';
describe('useOptimistic', () => {
it('shows optimistic update immediately', async () => {
const mockAddTodo = jest.fn(() => new Promise(resolve => setTimeout(resolve, 100)));
render(
<TodoList
todos={[]}
addTodo={mockAddTodo}
/>
);
fireEvent.change(screen.getByPlaceholderText('Add a todo'), {
target: { value: 'New todo' }
});
fireEvent.click(screen.getByText('Add'));
expect(screen.getByText('New todo')).toBeInTheDocument();
await waitFor(() => {
expect(mockAddTodo).toHaveBeenCalledWith({
id: expect.any(Number),
text: 'New todo'
});
});
});
});
Performance Best Practices
React Compiler Guidelines
- Write Standard React Code: The compiler works best with idiomatic React patterns
- Avoid Manual Memoization: Let the compiler handle useMemo, useCallback, and memo
- Keep Components Pure: Avoid side effects in render
- Use Stable References: Pass stable objects as props
function ProductCard({ product, onAddToCart }) {
const [quantity, setQuantity] = useState(1);
const handleAdd = () => {
onAddToCart(product.id, quantity);
};
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
<input
type="number"
value={quantity}
onChange={e => setQuantity(Number(e.target.value))}
min="1"
/>
<button onClick={handleAdd}>Add to Cart</button>
</div>
);
}
function ProductCard({ product, onAddToCart }) {
const [quantity, setQuantity] = useState(1);
const handleAdd = useCallback(() => {
onAddToCart(product.id, quantity);
}, [product.id, quantity, onAddToCart]);
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
<QuantityInput
value={quantity}
onChange={setQuantity}
/>
<button onClick={handleAdd}>Add to Cart</button>
</div>
);
}
Server Components Best Practices
- Keep Server Components Server-Only: No event handlers, hooks, or browser APIs
- Minimize Client Components: Only use 'use client' when necessary
- Pass Data as Props: Serialize data when passing from Server to Client
- Use Server Actions for Mutations: Keep data operations on the server
async function ProductPage({ id }: { id: string }) {
const product = await fetchProduct(id);
return (
<article>
<header>
<h1>{product.name}</h1>
<p>{product.description}</p>
</header>
<img
src={product.imageUrl}
alt={product.name}
width={600}
height={400}
/>
<PriceDisplay price={product.price} />
<AddToCartForm productId={product.id} />
</article>
);
}
'use client';
function AddToCartForm({ productId }: { productId: string }) {
const [isAdding, setIsAdding] = useState(false);
async function handleSubmit() {
setIsAdding(true);
await addToCart(productId);
setIsAdding(false);
}
return (
<form action={handleSubmit}>
<button type="submit" disabled={isAdding}>
{isAdding ? 'Adding...' : 'Add to Cart'}
</button>
</form>
);
}
Migration Guide
From React 18 to 19
- Update Dependencies:
npm install react@19 react-dom@19
-
Adopt Server Components:
- Identify data-fetching components
- Remove client-side code from Server Components
- Add 'use client' directive where needed
-
Replace Manual Optimistic Updates:
function TodoList({ todos, addTodo }) {
const [optimisticTodos, setOptimisticTodos] = useState(todos);
const handleAdd = async (text) => {
const newTodo = { id: Date.now(), text };
setOptimisticTodos([...optimisticTodos, newTodo]);
await addTodo(newTodo);
};
}
function TodoList({ todos, addTodo }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, newTodo]
);
const handleAdd = async (formData) => {
const newTodo = { id: Date.now(), text: formData.get('text') };
addOptimisticTodo(newTodo);
await addTodo(newTodo);
};
}
- Enable React Compiler:
- Install babel-plugin-react-compiler
- Remove manual memoization
- Let the compiler optimize automatically
References