DeveloperBootstrap

JavaScript

React Advanced Patterns Production Guide

Master advanced React patterns including hooks, context, render props, HOCs, custom hooks, performance optimisation, and production deployment with TypeScript.

What you'll learn

  • Implement compound component patterns
  • Build type-safe custom hooks
  • Use render props and HOCs correctly
  • Manage complex state with context + useReducer
  • Implement optimistic UI updates
  • Write accessible component APIs
  • Test advanced patterns with Vitest and Testing Library

Compound Components

Compound components share implicit state via context, giving consumers a flexible, ergonomic API without prop drilling.

React TSX
1import { createContext, useContext, useState } from 'react';
2
3interface TabsCtx { active: string; setActive: (v: string) => void }
4const TabsContext = createContext<TabsCtx | null>(null);
5
6function useTabs() {
7  const ctx = useContext(TabsContext);
8  if (!ctx) throw new Error('useTabs must be used inside <Tabs>');
9  return ctx;
10}
11
12function Tabs({ defaultValue, children }: { defaultValue: string; children: React.ReactNode }) {
13  const [active, setActive] = useState(defaultValue);
14  return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>;
15}
16
17function TabsList({ children }: { children: React.ReactNode }) {
18  return <div role="tablist" className="flex gap-2">{children}</div>;
19}
20
21function TabsTrigger({ value, children }: { value: string; children: React.ReactNode }) {
22  const { active, setActive } = useTabs();
23  return (
24    <button
25      role="tab"
26      aria-selected={active === value}
27      onClick={() => setActive(value)}
28      className={active === value ? 'font-bold border-b-2 border-primary' : 'text-muted-foreground'}
29    >
30      {children}
31    </button>
32  );
33}
34
35function TabsContent({ value, children }: { value: string; children: React.ReactNode }) {
36  const { active } = useTabs();
37  if (active !== value) return null;
38  return <div role="tabpanel">{children}</div>;
39}
40
41// Attach sub-components
42Tabs.List    = TabsList;
43Tabs.Trigger = TabsTrigger;
44Tabs.Content = TabsContent;
45
46// Usage
47<Tabs defaultValue="overview">
48  <Tabs.List>
49    <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
50    <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
51  </Tabs.List>
52  <Tabs.Content value="overview"><p>Overview content</p></Tabs.Content>
53  <Tabs.Content value="settings"><p>Settings content</p></Tabs.Content>
54</Tabs>

Custom Hooks

TypeScript
1// useDebounce — type-safe, generic
2import { useState, useEffect } from 'react';
3
4export function useDebounce<T>(value: T, delay: number): T {
5  const [debouncedValue, setDebouncedValue] = useState<T>(value);
6
7  useEffect(() => {
8    const handler = setTimeout(() => setDebouncedValue(value), delay);
9    return () => clearTimeout(handler);
10  }, [value, delay]);
11
12  return debouncedValue;
13}
14
15// useLocalStorage — SSR-safe
16export function useLocalStorage<T>(key: string, initialValue: T) {
17  const [storedValue, setStoredValue] = useState<T>(() => {
18    if (typeof window === 'undefined') return initialValue;
19    try {
20      const item = window.localStorage.getItem(key);
21      return item ? (JSON.parse(item) as T) : initialValue;
22    } catch {
23      return initialValue;
24    }
25  });
26
27  const setValue = (value: T | ((val: T) => T)) => {
28    const valueToStore = value instanceof Function ? value(storedValue) : value;
29    setStoredValue(valueToStore);
30    if (typeof window !== 'undefined') {
31      window.localStorage.setItem(key, JSON.stringify(valueToStore));
32    }
33  };
34
35  return [storedValue, setValue] as const;
36}

Render Props & HOCs

React TSX
1// Prefer custom hooks over HOCs for logic reuse.
2// HOCs remain useful for cross-cutting concerns like auth guards.
3
4function withAuth<P extends object>(WrappedComponent: React.ComponentType<P>) {
5  return function AuthGuard(props: P) {
6    const { isAuthenticated, isLoading } = useAuth();
7    if (isLoading) return <Spinner />;
8    if (!isAuthenticated) return <Navigate to="/login" replace />;
9    return <WrappedComponent {...props} />;
10  };
11}
12
13const ProtectedDashboard = withAuth(Dashboard);

Advanced State Management

TypeScript
1// Context + useReducer for complex domain state
2type Action =
3  | { type: 'INCREMENT'; by: number }
4  | { type: 'RESET' };
5
6function reducer(state: number, action: Action): number {
7  switch (action.type) {
8    case 'INCREMENT': return state + action.by;
9    case 'RESET':     return 0;
10  }
11}
12
13// For global client state, prefer Zustand:
14import { create } from 'zustand';
15
16interface CartStore {
17  items: CartItem[];
18  addItem: (item: CartItem) => void;
19  removeItem: (id: string) => void;
20}
21
22const useCartStore = create<CartStore>((set) => ({
23  items: [],
24  addItem: (item) => set((s) => ({ items: [...s.items, item] })),
25  removeItem: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
26}));

Optimistic UI

TypeScript
1import { useMutation, useQueryClient } from '@tanstack/react-query';
2
3function useLikeMutation() {
4  const queryClient = useQueryClient();
5
6  return useMutation({
7    mutationFn: (postId: string) =>
8      fetch(`${process.env['NEXT_PUBLIC_API_URL']}/posts/${postId}/like`, {
9        method: 'POST',
10        credentials: 'include',
11      }),
12
13    onMutate: async (postId) => {
14      await queryClient.cancelQueries({ queryKey: ['post', postId] });
15      const snapshot = queryClient.getQueryData(['post', postId]);
16      queryClient.setQueryData(['post', postId], (old: Post) => ({
17        ...old,
18        likes: old.likes + 1,
19        isLiked: true,
20      }));
21      return { snapshot };
22    },
23
24    onError: (_err, postId, ctx) => {
25      // Roll back on failure
26      queryClient.setQueryData(['post', postId], ctx?.snapshot);
27    },
28  });
29}

Testing Patterns

React TSX
1import { render, screen, userEvent } from '@testing-library/react';
2import { describe, it, expect } from 'vitest';
3import { Tabs } from './Tabs';
4
5describe('Tabs compound component', () => {
6  it('renders the default tab content', () => {
7    render(
8      <Tabs defaultValue="a">
9        <Tabs.List>
10          <Tabs.Trigger value="a">Tab A</Tabs.Trigger>
11          <Tabs.Trigger value="b">Tab B</Tabs.Trigger>
12        </Tabs.List>
13        <Tabs.Content value="a">Content A</Tabs.Content>
14        <Tabs.Content value="b">Content B</Tabs.Content>
15      </Tabs>
16    );
17    expect(screen.getByText('Content A')).toBeInTheDocument();
18    expect(screen.queryByText('Content B')).not.toBeInTheDocument();
19  });
20
21  it('switches tab on click', async () => {
22    const user = userEvent.setup();
23    render(/* same tree */);
24    await user.click(screen.getByRole('tab', { name: 'Tab B' }));
25    expect(screen.getByText('Content B')).toBeInTheDocument();
26  });
27});