Claude Code Plugins

Community-maintained marketplace

Feedback

Comprehensive React 19 patterns expert covering Server Components, Actions, use() hook, useOptimistic, useFormStatus, useFormState, React Compiler, concurrent features, Suspense, and modern TypeScript development. Proactively use for any React development, component architecture, state management, performance optimization, or when implementing React 19's latest features.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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. Proactively use for any React development, component architecture, state management, performance optimization, or when implementing React 19's latest features.
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();
    
    // Cleanup function
    return () => {
      connection.disconnect();
    };
  }, [roomId]); // Dependency array
  
  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]); // Re-run when either changes
  
  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);
    };
  }, []); // Empty array = run once on mount
  
  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:

// useOnlineStatus.ts
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;
}

// Usage in components
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:

// useChatRoom.ts
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]);
}

// Usage
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>
  );
}

// Usage
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]); // Unnecessary effect
  
  return <ul>{/* ... */}</ul>;
}

✅ Good - Compute during render:

function TodoList({ todos }: { todos: Todo[] }) {
  const visibleTodos = todos.filter(t => !t.completed); // Direct computation
  
  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]); // Only recompute when data changes
  
  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]); // Recreate only when count changes
  
  return <ExpensiveChild onClick={handleClick} />;
}

TypeScript Best Practices

Type-Safe Props

interface UserProps {
  id: string;
  name: string;
  email: string;
  age?: number; // Optional
}

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>
  );
}

// Usage
<List 
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
/>

Event Handlers

function Form() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // Handle form submission
  };
  
  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

  1. Dependency Arrays: Always specify correct dependencies in useEffect
  2. State Structure: Keep state minimal and avoid redundant state
  3. Component Size: Keep components small and focused
  4. Custom Hooks: Extract complex logic into reusable custom hooks
  5. TypeScript: Use TypeScript for type safety
  6. Keys: Use stable IDs as keys for list items, not array indices
  7. Immutability: Never mutate state directly
  8. Effects: Use effects only for synchronization with external systems
  9. Performance: Profile before optimizing with useMemo/useCallback

React 19 Specific Best Practices

  1. Server Components: Use Server Components for data fetching and static content
  2. Client Components: Mark components as 'use client' only when necessary
  3. Actions: Use Server Actions for mutations and form submissions
  4. Optimistic Updates: Implement useOptimistic for better UX
  5. use() Hook: Use for reading promises and context conditionally
  6. Form State: Use useFormState and useFormStatus for complex forms
  7. Concurrent Features: Leverage useTransition for non-urgent updates
  8. Error Boundaries: Implement proper error handling with error boundaries

Common Pitfalls

General React Pitfalls

Missing Dependencies:

useEffect(() => {
  // Uses 'count' but doesn't include it in deps
  console.log(count);
}, []); // Wrong!

Mutating State:

const [items, setItems] = useState([]);
items.push(newItem); // Wrong! Mutates state
setItems(items); // Won't trigger re-render

Correct Approach:

setItems([...items, newItem]); // Create new array

React 19 Specific Pitfalls

Using use() outside of render:

// Wrong!
function handleClick() {
  const data = use(promise); // Error: use() can only be called in render
}

Correct usage:

function Component({ promise }) {
  const data = use(promise); // Correct: called during render
  return <div>{data}</div>;
}

Forgetting 'use server' directive:

// Wrong - missing 'use server'
export async function myAction() {
  // This will run on the client!
}

Correct Server Action:

'use server'; // Must be at the top

export async function myAction() {
  // Now runs on the server
}

Mixing Server and Client logic incorrectly:

// Wrong - trying to use browser APIs in Server Component
export default async function ServerComponent() {
  const width = window.innerWidth; // Error: window is not defined
  return <div>{width}</div>;
}

Correct separation:

// Server Component for data
export default async function ServerComponent() {
  const data = await fetchData();
  return <ClientComponent data={data} />;
}

// Client Component for browser APIs
'use client';

function ClientComponent({ data }) {
  const [width, setWidth] = useState(window.innerWidth);
  // Handle resize logic...
  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';

// Reading a Promise in a component
function MessageComponent({ messagePromise }) {
  const message = use(messagePromise);
  return <p>{message}</p>;
}

// Reading Context conditionally
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') };

    // Optimistically add to UI
    addOptimisticTodo(newTodo);

    // Actually add to backend
    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:

// app/actions.ts
'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;

  // Validate input
  if (!title || !content) {
    return { error: 'Title and content are required' };
  }

  // Save to database
  const post = await db.post.create({
    data: { title, content }
  });

  // Update cache and redirect
  revalidatePath('/posts');
  redirect(`/posts/${post.id}`);
}

Server Components

Components that run exclusively on the server:

// app/posts/page.tsx - Server Component
async function PostsPage() {
  // Server-side data fetching
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10
  });

  return (
    <div>
      <h1>Latest Posts</h1>
      <PostsList posts={posts} />
    </div>
  );
}

// Client Component for interactivity
'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:

// Before React Compiler - manual memoization needed
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>
  );
});

// After React Compiler - no manual optimization needed
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

# Install React Compiler
npm install -D babel-plugin-react-compiler@latest

# Install ESLint plugin for validation
npm install -D eslint-plugin-react-hooks@latest
// babel.config.js
module.exports = {
  plugins: [
    'babel-plugin-react-compiler', // Must run first!
    // ... other plugins
  ],
};
// vite.config.js for Vite users
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
});

Compiler Configuration

// babel.config.js with compiler options
module.exports = {
  plugins: [
    [
      'babel-plugin-react-compiler',
      {
        // Enable compilation for specific files
        target: '18', // or '19'
        // Debug mode for development
        debug: process.env.NODE_ENV === 'development'
      }
    ],
  ],
};

// Incremental adoption with overrides
module.exports = {
  plugins: [],
  overrides: [
    {
      test: './src/components/**/*.{js,jsx,ts,tsx}',
      plugins: ['babel-plugin-react-compiler']
    }
  ]
};

Advanced Server Components Patterns

Mixed Server/Client Architecture

// Server Component for data fetching
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>
  );
}

// Client Component for interactivity
'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
) {
  // Extract and validate data
  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 {
    // Process payment
    const order = await createOrder(result.data);

    // Update inventory
    await updateInventory(result.data.items);

    // Send confirmation
    await sendConfirmationEmail(order);

    // Revalidate cache
    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>) => {
    // Update input immediately
    setQuery(e.target.value);

    // Transition the filter operation
    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';

// Mock server action
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'));

    // Optimistic update appears immediately
    expect(screen.getByText('New todo')).toBeInTheDocument();

    // Wait for actual submission
    await waitFor(() => {
      expect(mockAddTodo).toHaveBeenCalledWith({
        id: expect.any(Number),
        text: 'New todo'
      });
    });
  });
});

Performance Best Practices

React Compiler Guidelines

  1. Write Standard React Code: The compiler works best with idiomatic React patterns
  2. Avoid Manual Memoization: Let the compiler handle useMemo, useCallback, and memo
  3. Keep Components Pure: Avoid side effects in render
  4. Use Stable References: Pass stable objects as props
// Good: Clean, idiomatic React
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>
  );
}

// Avoid: Manual optimization
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

  1. Keep Server Components Server-Only: No event handlers, hooks, or browser APIs
  2. Minimize Client Components: Only use 'use client' when necessary
  3. Pass Data as Props: Serialize data when passing from Server to Client
  4. Use Server Actions for Mutations: Keep data operations on the server
// Good: Server Component for static content
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>
  );
}

// Client Component only for interactivity
'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

  1. Update Dependencies:
npm install react@19 react-dom@19
  1. Adopt Server Components:

    • Identify data-fetching components
    • Remove client-side code from Server Components
    • Add 'use client' directive where needed
  2. Replace Manual Optimistic Updates:

// Before
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);
  };
}

// After
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);
  };
}
  1. Enable React Compiler:
    • Install babel-plugin-react-compiler
    • Remove manual memoization
    • Let the compiler optimize automatically

References