All topics
Frontend · Learning hub

React notes for developers

Master React with a curated set of 7 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Frontend notes
React

Hooks Deep Dive

React Hooks Deep Dive Comprehensive guide to React hooks - the modern way to manage state and side effects in functional components: useState Hook useState is t

React Hooks Deep Dive

Comprehensive guide to React hooks - the modern way to manage state and side effects in functional components:

useState Hook

useState is the most fundamental hook for managing local component state. It returns a stateful value and a function to update it.

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="Enter name"
      />
    </div>
  );
}

Functional Updates

When the new state depends on the previous state, use a function to ensure you get the latest value:

// Good - functional update
setCount(prevCount => prevCount + 1);

// Avoid - might use stale state
setCount(count + 1);

useEffect Hook

useEffect lets you perform side effects in functional components. It combines componentDidMount, componentDidUpdate, and componentWillUnmount.

Basic useEffect

useEffect(() => {
  // Runs after every render
  document.title = `Count: ${count}`;
});

useEffect with Dependencies

useEffect(() => {
  // Runs only when count changes
  document.title = `Count: ${count}`;
}, [count]);

useEffect with Cleanup

useEffect(() => {
  const timer = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);
  
  return () => {
    clearInterval(timer);
  };
}, []);

Mount/Unmount Effect

useEffect(() => {
  // Runs only on mount
  console.log('Component mounted');
  
  return () => {
    // Runs on unmount
    console.log('Component unmounted');
  };
}, []);

useContext Hook

useContext provides a way to pass data through the component tree without having to pass props down manually at every level.

// Create context
const ThemeContext = createContext();

// Provider component
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Header />
    </ThemeContext.Provider>
  );
}

// Consumer component
function Header() {
  const theme = useContext(ThemeContext);
  return <h1 className={theme}>Header</h1>;
}

useReducer Hook

useReducer is an alternative to useState for managing complex state logic. It follows the reducer pattern and is useful when state updates depend on previous state.

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </div>
  );
}

useMemo Hook

useMemo memoizes the result of a computation and only recalculates when dependencies change. It helps optimize expensive calculations.

function ExpensiveComponent({ items }) {
  const expensiveValue = useMemo(() => {
    return items.reduce((sum, item) => sum + item.value, 0);
  }, [items]);
  
  return <div>Total: {expensiveValue}</div>;
}

useCallback Hook

useCallback memoizes a function and only creates a new function when dependencies change. It helps prevent unnecessary re-renders of child components.

function Parent({ items }) {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <Child onClick={handleClick} />
    </div>
  );
}

useRef Hook

useRef returns a mutable ref object that persists for the full lifetime of the component. It can be used to access DOM elements or store mutable values.

function TextInput() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    inputRef.current.focus();
  };
  
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

Custom Hooks

Custom hooks are JavaScript functions that start with "use" and can call other hooks. They allow you to extract component logic into reusable functions.

// Custom hook
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = useCallback(() => setCount(c => c + 1), []);
  const decrement = useCallback(() => setCount(c => c - 1), []);
  const reset = useCallback(() => setCount(initialValue), [initialValue]);
  
  return { count, increment, decrement, reset };
}

// Using the custom hook
function Counter() {
  const { count, increment, decrement, reset } = useCounter(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Hook Rules

  • Only call hooks at the top level of React functions

  • Don't call hooks inside loops, conditions, or nested functions

  • Only call hooks from React function components or custom hooks

Common Hook Patterns

Data Fetching Hook

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [url]);
  
  return { data, loading, error };
}

Local Storage Hook

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}
React

Performance Optimization

React Performance Optimization Comprehensive guide to optimizing React applications for better performance, user experience, and scalability: React.memo() - Pre

React Performance Optimization

Comprehensive guide to optimizing React applications for better performance, user experience, and scalability:

React.memo() - Preventing Unnecessary Re-renders

React.memo() is a higher-order component that memoizes the result of a component. It only re-renders if its props have changed, helping to prevent unnecessary re-renders.

// Basic memo usage
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
  console.log('Rendering expensive component');
  return (
    <div>
      <h2>{data.title}</h2>
      <p>{data.description}</p>
      <button onClick={onUpdate}>Update</button>
    </div>
  );
});

// Custom comparison function
const CustomMemoComponent = React.memo(
  ({ user, settings }) => {
    return <UserProfile user={user} settings={settings} />;
  },
  (prevProps, nextProps) => {
    // Custom comparison logic
    return prevProps.user.id === nextProps.user.id &&
           prevProps.settings.theme === nextProps.settings.theme;
  }
);

useMemo() - Memoizing Expensive Calculations

useMemo memoizes the result of a computation and only recalculates when dependencies change. It helps optimize expensive calculations and object creation.

function ProductList({ products, filters, sortBy }) {
  // Expensive calculation - only runs when dependencies change
  const filteredAndSortedProducts = useMemo(() => {
    console.log('Filtering and sorting products');
    
    return products
      .filter(product => {
        return filters.category === 'all' || product.category === filters.category;
      })
      .filter(product => product.price >= filters.minPrice)
      .sort((a, b) => {
        if (sortBy === 'price') return a.price - b.price;
        if (sortBy === 'name') return a.name.localeCompare(b.name);
        return 0;
      });
  }, [products, filters, sortBy]);
  
  return (
    <div>
      {filteredAndSortedProducts.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

useCallback() - Memoizing Functions

useCallback memoizes a function and only creates a new function when dependencies change. It helps prevent unnecessary re-renders of child components that depend on the function.

function TodoApp({ todos }) {
  const [filter, setFilter] = useState('all');
  
  // Memoized callback - only changes when todos change
  const handleToggleTodo = useCallback((id) => {
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  }, []);
  
  // Memoized callback with dependencies
  const handleAddTodo = useCallback((text) => {
    const newTodo = {
      id: Date.now(),
      text,
      completed: false,
      createdAt: new Date()
    };
    setTodos(prevTodos => [...prevTodos, newTodo]);
  }, []);
  
  return (
    <div>
      <TodoList 
        todos={todos}
        onToggle={handleToggleTodo}
        onAdd={handleAddTodo}
      />
    </div>
  );
}

Code Splitting with React.lazy()

Code splitting allows you to split your code into smaller chunks that can be loaded on demand, reducing the initial bundle size and improving performance.

import { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router-dom';

// Lazy load components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

// Loading component
const PageLoader = () => (
  <div className="flex items-center justify-center h-64">
    <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
    <span className="ml-2">Loading...</span>
  </div>
);

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  );
}

Virtual Scrolling for Large Lists

Virtual scrolling renders only the visible items in a large list, dramatically improving performance for lists with thousands of items.

import { FixedSizeList as List } from 'react-window';

const VirtualizedList = ({ items }) => {
  const Row = ({ index, style }) => (
    <div style={style} className="flex items-center p-4 border-b">
      <div className="w-8 h-8 bg-gray-300 rounded-full mr-4"></div>
      <div>
        <h3 className="font-semibold">{items[index].name}</h3>
        <p className="text-gray-600">{items[index].email}</p>
      </div>
    </div>
  );
  
  return (
    <List
      height={600}
      itemCount={items.length}
      itemSize={80}
      width="100%"
    >
      {Row}
    </List>
  );
};

// Usage
function App() {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    // Load 10,000 users
    const loadUsers = async () => {
      const response = await fetch('/api/users?limit=10000');
      const data = await response.json();
      setUsers(data);
    };
    loadUsers();
  }, []);
  
  return <VirtualizedList items={users} />;
}

Bundle Analysis and Optimization

Analyzing your bundle size helps identify optimization opportunities and reduce the initial load time.

# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer

# Analyze bundle size
npm run build
npm run analyze

# Or with webpack-bundle-analyzer
npx webpack-bundle-analyzer build/static/js/*.js

Performance Monitoring

Monitor your React application performance using React DevTools Profiler and real user monitoring tools.

// React DevTools Profiler API
import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
  console.log('Profiler:', {
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime
  });
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <Header />
      <MainContent />
      <Footer />
    </Profiler>
  );
}

// Performance monitoring with Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  // Send to your analytics service
  console.log(metric);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

Common Performance Anti-patterns

  • Creating objects/arrays in render methods

  • Inline function definitions in JSX

  • Overusing useEffect without proper dependencies

  • Not memoizing expensive calculations

Performance Best Practices

  • Use React.memo() for components that receive the same props frequently

  • Implement code splitting for route-based components

  • Use virtual scrolling for large lists

  • Optimize images with lazy loading and proper formats

  • Monitor and measure performance regularly

React

State Management

React State Management Comprehensive guide to managing state in React applications, from simple local state to complex global state management: Local State with

React State Management

Comprehensive guide to managing state in React applications, from simple local state to complex global state management:

Local State with useState

useState is the foundation of React state management. It's perfect for component-level state that doesn't need to be shared.

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
      
      <input 
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <p>Hello, {name || 'Anonymous'}!</p>
    </div>
  );
}

Complex Local State with useReducer

useReducer is ideal for managing complex state logic with multiple sub-values or when the next state depends on the previous one.

const initialState = {
  todos: [],
  filter: 'all',
  loading: false,
  error: null
};

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, {
          id: Date.now(),
          text: action.text,
          completed: false
        }]
      };
    
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.id
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    
    case 'SET_FILTER':
      return { ...state, filter: action.filter };
    
    case 'SET_LOADING':
      return { ...state, loading: action.loading };
    
    case 'SET_ERROR':
      return { ...state, error: action.error };
    
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  
  const addTodo = (text) => {
    dispatch({ type: 'ADD_TODO', text });
  };
  
  const toggleTodo = (id) => {
    dispatch({ type: 'TOGGLE_TODO', id });
  };
  
  const setFilter = (filter) => {
    dispatch({ type: 'SET_FILTER', filter });
  };
  
  return (
    <div>
      <TodoForm onAdd={addTodo} />
      <TodoFilter filter={state.filter} onFilterChange={setFilter} />
      <TodoList 
        todos={state.todos}
        filter={state.filter}
        onToggle={toggleTodo}
      />
    </div>
  );
}

Context API for Global State

The Context API provides a way to pass data through the component tree without having to pass props down manually at every level.

// Create context
const ThemeContext = createContext();
const UserContext = createContext();

// Theme provider
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  const value = {
    theme,
    toggleTheme,
    colors: theme === 'light' ? lightColors : darkColors
  };
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

// User provider
function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Load user from API
    loadUser().then(userData => {
      setUser(userData);
      setLoading(false);
    });
  }, []);
  
  const login = async (credentials) => {
    const userData = await authenticate(credentials);
    setUser(userData);
  };
  
  const logout = () => {
    setUser(null);
  };
  
  return (
    <UserContext.Provider value={{ user, login, logout, loading }}>
      {children}
    </UserContext.Provider>
  );
}

// Custom hooks for consuming context
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
}

// Usage in components
function App() {
  return (
    <ThemeProvider>
      <UserProvider>
        <Header />
        <MainContent />
        <Footer />
      </UserProvider>
    </ThemeProvider>
  );
}

function Header() {
  const { theme, toggleTheme } = useTheme();
  const { user, logout } = useUser();
  
  return (
    <header className={`header-${theme}`}>
      <h1>My App</h1>
      <button onClick={toggleTheme}>
        Switch to {theme === 'light' ? 'dark' : 'light'} mode
      </button>
      {user && (
        <div>
          <span>Welcome, {user.name}!</span>
          <button onClick={logout}>Logout</button>
        </div>
      )}
    </header>
  );
}

Redux - Predictable State Container

Redux is a predictable state container for JavaScript apps. It helps you write applications that behave consistently and are easy to test.

// Store setup
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { createSlice, configureStore } from '@reduxjs/toolkit';

// Redux Toolkit approach (recommended)
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

const todosSlice = createSlice({
  name: 'todos',
  initialState: { items: [] },
  reducers: {
    addTodo: (state, action) => {
      state.items.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.items.find(item => item.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
  },
});

// Configure store
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
    todos: todosSlice.reducer,
  },
});

// React component with Redux
import { useSelector, useDispatch } from 'react-redux';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();
  
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => dispatch(counterSlice.actions.increment())}>
        +
      </button>
      <button onClick={() => dispatch(counterSlice.actions.decrement())}>
        -
      </button>
    </div>
  );
}

Zustand - Lightweight State Management

Zustand is a small, fast and scalable state management solution with a comfy API based on hooks and isn't boilerplate-heavy.

import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

// Simple store
const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// Complex store with middleware
const useTodoStore = create(
  devtools(
    persist(
      (set, get) => ({
        todos: [],
        filter: 'all',
        
        addTodo: (text) =>
          set(
            (state) => ({
              todos: [
                ...state.todos,
                {
                  id: Date.now(),
                  text,
                  completed: false,
                  createdAt: new Date(),
                },
              ],
            }),
            false,
            'addTodo'
          ),
        
        toggleTodo: (id) =>
          set(
            (state) => ({
              todos: state.todos.map((todo) =>
                todo.id === id ? { ...todo, completed: !todo.completed } : todo
              ),
            }),
            false,
            'toggleTodo'
          ),
        
        setFilter: (filter) => set({ filter }, false, 'setFilter'),
        
        getFilteredTodos: () => {
          const { todos, filter } = get();
          switch (filter) {
            case 'active':
              return todos.filter((todo) => !todo.completed);
            case 'completed':
              return todos.filter((todo) => todo.completed);
            default:
              return todos;
          }
        },
        
        clearCompleted: () =>
          set(
            (state) => ({
              todos: state.todos.filter((todo) => !todo.completed),
            }),
            false,
            'clearCompleted'
          ),
      }),
      {
        name: 'todo-storage',
        partialize: (state) => ({ todos: state.todos }),
      }
    ),
    {
      name: 'todo-store',
    }
  )
);

// Usage in components
function TodoApp() {
  const {
    todos,
    filter,
    addTodo,
    toggleTodo,
    setFilter,
    getFilteredTodos,
    clearCompleted,
  } = useTodoStore();
  
  const filteredTodos = getFilteredTodos();
  
  return (
    <div>
      <TodoForm onAdd={addTodo} />
      <TodoFilter filter={filter} onFilterChange={setFilter} />
      <TodoList todos={filteredTodos} onToggle={toggleTodo} />
      <button onClick={clearCompleted}>Clear Completed</button>
    </div>
  );
}

Jotai - Atomic State Management

Jotai takes a bottom-up approach to React state management with an atomic model inspired by Recoil. It's perfect for fine-grained reactivity.

import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// Basic atoms
const countAtom = atom(0);
const nameAtom = atom('');

// Derived atoms
const doubledCountAtom = atom((get) => get(countAtom) * 2);
const greetingAtom = atom((get) => {
  const name = get(nameAtom);
  return name ? `Hello, ${name}!` : 'Hello, Anonymous!';
});

// Async atoms
const userAtom = atom(async () => {
  const response = await fetch('/api/user');
  return response.json();
});

// Storage atoms
const themeAtom = atomWithStorage('theme', 'light');
const settingsAtom = atomWithStorage('settings', {
  notifications: true,
  language: 'en',
});

// Component usage
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const doubledCount = useAtomValue(doubledCountAtom);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubledCount}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <button onClick={() => setCount(c => c - 1)}>-</button>
    </div>
  );
}

function Greeting() {
  const [name, setName] = useAtom(nameAtom);
  const greeting = useAtomValue(greetingAtom);
  
  return (
    <div>
      <input 
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <p>{greeting}</p>
    </div>
  );
}

State Management Patterns

Lifting State Up

When multiple components need to share state, lift the state up to their common parent component.

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState('all');
  
  const addTodo = (text) => {
    setTodos(prev => [...prev, { id: Date.now(), text, completed: false }]);
  };
  
  const toggleTodo = (id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };
  
  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });
  
  return (
    <div>
      <TodoForm onAdd={addTodo} />
      <TodoFilter filter={filter} onFilterChange={setFilter} />
      <TodoList todos={filteredTodos} onToggle={toggleTodo} />
    </div>
  );
}

Custom Hooks for State Logic

Extract state logic into custom hooks to make it reusable across components.

// Custom hook for form state
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  const handleChange = (name, value) => {
    setValues(prev => ({ ...prev, [name]: value }));
    
    // Clear error when user starts typing
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };
  
  const handleBlur = (name) => {
    setTouched(prev => ({ ...prev, [name]: true }));
  };
  
  const validate = (validationRules) => {
    const newErrors = {};
    
    Object.keys(validationRules).forEach(field => {
      const rule = validationRules[field];
      const value = values[field];
      
      if (rule.required && !value) {
        newErrors[field] = `${field} is required`;
      } else if (rule.minLength && value.length < rule.minLength) {
        newErrors[field] = `${field} must be at least ${rule.minLength} characters`;
      } else if (rule.pattern && !rule.pattern.test(value)) {
        newErrors[field] = `${field} format is invalid`;
      }
    });
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const reset = () => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
  };
  
  return {
    values,
    errors,
    touched,
    handleChange,
    handleBlur,
    validate,
    reset,
  };
}

// Usage in component
function ContactForm() {
  const {
    values,
    errors,
    touched,
    handleChange,
    handleBlur,
    validate,
    reset,
  } = useForm({
    name: '',
    email: '',
    message: ''
  });
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    const isValid = validate({
      name: { required: true, minLength: 2 },
      email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
      message: { required: true, minLength: 10 }
    });
    
    if (isValid) {
      // Submit form
      console.log('Form submitted:', values);
      reset();
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          name="name"
          value={values.name}
          onChange={(e) => handleChange('name', e.target.value)}
          onBlur={() => handleBlur('name')}
          placeholder="Your name"
        />
        {touched.name && errors.name && <span>{errors.name}</span>}
      </div>
      
      <div>
        <input
          name="email"
          type="email"
          value={values.email}
          onChange={(e) => handleChange('email', e.target.value)}
          onBlur={() => handleBlur('email')}
          placeholder="Your email"
        />
        {touched.email && errors.email && <span>{errors.email}</span>}
      </div>
      
      <div>
        <textarea
          name="message"
          value={values.message}
          onChange={(e) => handleChange('message', e.target.value)}
          onBlur={() => handleBlur('message')}
          placeholder="Your message"
        />
        {touched.message && errors.message && <span>{errors.message}</span>}
      </div>
      
      <button type="submit">Send Message</button>
    </form>
  );
}

MobX - Simple Reactive State Management

MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming. It uses observables to automatically track state dependencies.

import { makeObservable, observable, action, computed } from 'mobx';
import { observer } from 'mobx-react-lite';

// Store class
class TodoStore {
  todos = [];
  filter = 'all';
  
  constructor() {
    makeObservable(this, {
      todos: observable,
      filter: observable,
      addTodo: action,
      toggleTodo: action,
      setFilter: action,
      filteredTodos: computed,
      completedCount: computed,
    });
  }
  
  addTodo(text) {
    this.todos.push({
      id: Date.now(),
      text,
      completed: false,
    });
  }
  
  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  }
  
  setFilter(filter) {
    this.filter = filter;
  }
  
  get filteredTodos() {
    switch (this.filter) {
      case 'active':
        return this.todos.filter(t => !t.completed);
      case 'completed':
        return this.todos.filter(t => t.completed);
      default:
        return this.todos;
    }
  }
  
  get completedCount() {
    return this.todos.filter(t => t.completed).length;
  }
}

// Create store instance
const todoStore = new TodoStore();

// Observer component - automatically re-renders when observables change
const TodoApp = observer(() => {
  const [inputValue, setInputValue] = useState('');
  
  const handleAdd = () => {
    if (inputValue.trim()) {
      todoStore.addTodo(inputValue);
      setInputValue('');
    }
  };
  
  return (
    <div>
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="Add todo"
      />
      <button onClick={handleAdd}>Add</button>
      
      <div>
        <button onClick={() => todoStore.setFilter('all')}>All</button>
        <button onClick={() => todoStore.setFilter('active')}>Active</button>
        <button onClick={() => todoStore.setFilter('completed')}>Completed</button>
      </div>
      
      <ul>
        {todoStore.filteredTodos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => todoStore.toggleTodo(todo.id)}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
      
      <p>Completed: {todoStore.completedCount} / {todoStore.todos.length}</p>
    </div>
  );
});

TanStack Query (React Query) - Server State Management

TanStack Query (formerly React Query) is a powerful library for fetching, caching, and updating server state in React applications. It eliminates boilerplate and provides automatic background refetching, caching, and synchronization.

Basic Usage

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Fetch data with useQuery
function UserProfile({ userId }) {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['user', userId],
    queryFn: async () => {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) throw new Error('Failed to fetch user');
      return response.json();
    },
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
  });
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
      <button onClick={() => refetch()}>Refresh</button>
    </div>
  );
}

Mutations

import { useMutation, useQueryClient } from '@tanstack/react-query';

function TodoForm() {
  const [text, setText] = useState('');
  const queryClient = useQueryClient();
  
  const addTodoMutation = useMutation({
    mutationFn: async (newTodo) => {
      const response = await fetch('/api/todos', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newTodo),
      });
      return response.json();
    },
    onSuccess: () => {
      // Invalidate and refetch todos
      queryClient.invalidateQueries({ queryKey: ['todos'] });
      setText('');
    },
    onError: (error) => {
      alert(`Error: ${error.message}`);
    },
  });
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      addTodoMutation.mutate({ text, completed: false });
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add todo"
        disabled={addTodoMutation.isPending}
      />
      <button type="submit" disabled={addTodoMutation.isPending}>
        {addTodoMutation.isPending ? 'Adding...' : 'Add Todo'}
      </button>
    </form>
  );
}

Advanced Patterns

import { useQuery, useQueries, useInfiniteQuery } from '@tanstack/react-query';

// Parallel queries
function Dashboard({ userId }) {
  const userQuery = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });
  
  const postsQuery = useQuery({
    queryKey: ['posts', userId],
    queryFn: () => fetchPosts(userId),
  });
  
  const statsQuery = useQuery({
    queryKey: ['stats', userId],
    queryFn: () => fetchStats(userId),
  });
  
  if (userQuery.isLoading || postsQuery.isLoading || statsQuery.isLoading) {
    return <div>Loading...</div>;
  }
  
  return (
    <div>
      <UserInfo user={userQuery.data} />
      <PostsList posts={postsQuery.data} />
      <StatsPanel stats={statsQuery.data} />
    </div>
  );
}

// Infinite scroll
function InfinitePosts() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: async ({ pageParam = 1 }) => {
      const response = await fetch(`/api/posts?page=${pageParam}`);
      return response.json();
    },
    getNextPageParam: (lastPage, pages) => {
      return lastPage.hasMore ? pages.length + 1 : undefined;
    },
  });
  
  return (
    <div>
      {data?.pages.map((page, i) => (
        <div key={i}>
          {page.posts.map(post => (
            <PostCard key={post.id} post={post} />
          ))}
        </div>
      ))}
      
      {hasNextPage && (
        <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? 'Loading more...' : 'Load More'}
        </button>
      )}
    </div>
  );
}

Choosing the Right State Management Solution

  • useState: Simple component state, form inputs, UI toggles

  • useReducer: Complex state logic, multiple related state values

  • Context API: Theme, authentication, user preferences

  • Redux: Large applications, complex state, time-travel debugging

  • Zustand: Simple global state, minimal boilerplate

  • Jotai: Fine-grained reactivity, atomic state updates

  • MobX: Reactive programming, automatic dependency tracking

  • TanStack Query: Server state management, caching, background sync

React

Interview Questions

React Interview Questions Comprehensive React interview questions covering fundamentals, advanced concepts, and practical scenarios: Fundamentals 1. What is Rea

React Interview Questions

Comprehensive React interview questions covering fundamentals, advanced concepts, and practical scenarios:

Fundamentals

1. What is React and why use it?

React is a JavaScript library for building user interfaces, particularly web applications. It uses a component-based architecture, virtual DOM for efficient updates, and a unidirectional data flow. Benefits include reusable components, better performance, and a large ecosystem.

2. What is JSX?

JSX (JavaScript XML) is a syntax extension that allows you to write HTML-like code in JavaScript. It gets transpiled to React.createElement() calls and makes component code more readable and maintainable.

3. What is the Virtual DOM?

The Virtual DOM is a JavaScript representation of the real DOM. React uses it to optimize rendering by comparing the virtual DOM with the previous version and only updating the parts that changed in the real DOM, making updates more efficient.

4. What is the difference between props and state?

  • Props: Read-only data passed from parent to child components

  • State: Mutable data managed within the component

  • Props flow down, state flows up

5. What are React keys and why are they important?

Keys help React identify which items have changed, been added, or removed in lists. They should be unique among siblings and stable across re-renders. Without keys, React may incorrectly reuse DOM elements, leading to performance issues and bugs.

Components & Lifecycle

6. What is the difference between functional and class components?

  • Functional: Modern approach using functions and hooks

  • Class: Traditional approach using ES6 classes and lifecycle methods

  • Functional components are preferred in modern React development

7. What are the different phases of the component lifecycle?

  • Mounting: Component is created and inserted into the DOM

  • Updating: Component re-renders due to state or props changes

  • Unmounting: Component is removed from the DOM

8. What is component composition?

Component composition is the pattern of building complex UIs by combining simpler components. It promotes reusability, maintainability, and follows the single responsibility principle.

Hooks

9. What are React hooks?

Hooks are functions that let you use state and other React features in functional components. They allow you to reuse stateful logic between components without changing component hierarchy.

10. What is useState and how does it work?

useState is a hook that returns a stateful value and a function to update it. It takes an initial value and returns an array with the current state and a setter function.

const [count, setCount] = useState(0);
const [name, setName] = useState('');

11. What is useEffect and when to use it?

useEffect lets you perform side effects in functional components. It combines componentDidMount, componentDidUpdate, and componentWillUnmount from class components.

useEffect(() => {
  // Side effect
  return () => {
    // Cleanup
  };
}, [dependencies]);

12. What is useContext?

useContext provides a way to pass data through the component tree without having to pass props down manually at every level. It consumes a context created by React.createContext().

13. What is useReducer and when to use it?

useReducer is an alternative to useState for managing complex state logic. It follows the reducer pattern and is useful when state updates depend on previous state or when you have multiple sub-values.

14. What is useMemo and useCallback?

  • useMemo: Memoizes the result of a computation

  • useCallback: Memoizes a function to prevent unnecessary re-renders

Performance & Optimization

15. How do you optimize React performance?

  • React.memo() for preventing unnecessary re-renders

  • useMemo() and useCallback() for expensive calculations

  • Code splitting with React.lazy() and Suspense

  • Virtual scrolling for large lists

16. What is React.memo()?

React.memo() is a higher-order component that memoizes the result of a component. It only re-renders if its props have changed, helping to prevent unnecessary re-renders.

17. What is code splitting in React?

Code splitting is the technique of splitting your code into smaller chunks that can be loaded on demand. React.lazy() and Suspense make it easy to implement code splitting for components.

State Management

18. What are the different ways to manage state in React?

  • Local state with useState

  • Context API for sharing state across components

  • External libraries like Redux, Zustand, or Jotai

19. What is Redux and when to use it?

Redux is a predictable state container for JavaScript apps. It centralizes application state and makes state changes predictable through pure functions called reducers. Use it when you have complex state logic or need to share state across many components.

20. What is the Context API?

The Context API provides a way to pass data through the component tree without having to pass props down manually at every level. It's useful for sharing global data like themes, user authentication, or language preferences.

Advanced Concepts

21. What are Higher-Order Components (HOCs)?

HOCs are functions that take a component and return a new component with additional functionality. They are a pattern for reusing component logic, though hooks are now preferred for most use cases.

22. What are Render Props?

Render props is a pattern where a component accepts a function as a prop and calls it to determine what to render. It's another way to share code between components.

23. What are Custom Hooks?

Custom hooks are JavaScript functions that start with "use" and can call other hooks. They allow you to extract component logic into reusable functions.

24. What is the difference between controlled and uncontrolled components?

  • Controlled: Form data is handled by React state

  • Uncontrolled: Form data is handled by the DOM itself

25. What is the difference between React and ReactDOM?

React is the core library that defines components and their behavior. ReactDOM is the package that provides DOM-specific methods for rendering React components to the DOM.

Testing & Debugging

26. How do you test React components?

  • Jest for unit testing and mocking

  • React Testing Library for component testing

  • Cypress or Playwright for end-to-end testing

27. What is the difference between shallow and deep testing?

  • Shallow: Tests component in isolation without child components

  • Deep: Tests component with all its child components

Practical Scenarios

28. How would you handle forms in React?

Forms can be handled using controlled components with useState, form libraries like Formik or React Hook Form, or uncontrolled components with useRef. The choice depends on complexity and requirements.

29. How would you implement authentication in React?

  • Context API for global auth state

  • Protected routes with conditional rendering

  • JWT tokens for API authentication

30. How would you handle API calls in React?

  • useEffect for side effects

  • Custom hooks for reusable API logic

  • Libraries like React Query or SWR for data fetching

Common Pitfalls & Best Practices

31. What are common React anti-patterns?

  • Mutating state directly

  • Using array index as key

  • Creating components inside render methods

  • Overusing useEffect

32. What are React best practices?

  • Keep components small and focused

  • Use functional components and hooks

  • Implement proper error boundaries

  • Use TypeScript for better type safety

React

Core Concepts

React Core Concepts Master the fundamental concepts that power React - the foundation you need to build modern, efficient web applications: What is React? React

React Core Concepts

Master the fundamental concepts that power React - the foundation you need to build modern, efficient web applications:

What is React?

React is a JavaScript library for building user interfaces. Created by Facebook (now Meta) in 2013, it revolutionized front-end development with its component-based architecture and efficient rendering through the Virtual DOM.

Components - Building Blocks of React

Components are independent, reusable pieces of UI. They accept inputs (props) and return React elements describing what should appear on screen.

Functional Components

Functional components are JavaScript functions that return JSX. They are the modern, preferred way to write React components.

// Simple functional component
function Welcome({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// Component with multiple elements
function UserCard({ user }) {
  return (
    <div className="user-card">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <p>Joined: {new Date(user.joinedAt).toLocaleDateString()}</p>
    </div>
  );
}

// Component with conditional rendering
function Greeting({ user }) {
  if (!user) {
    return <h1>Welcome, Guest!</h1>;
  }
  
  return (
    <div>
      <h1>Welcome back, {user.name}!</h1>
      <p>Last login: {user.lastLogin}</p>
    </div>
  );
}

Class Components (Legacy)

Class components were the original way to write stateful components. While functional components with hooks are now preferred, you may encounter class components in legacy code.

import { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }
  
  componentDidMount() {
    console.log('Component mounted');
  }
  
  componentWillUnmount() {
    console.log('Component will unmount');
  }
  
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

JSX (JavaScript XML)

JSX is a syntax extension that allows you to write HTML-like code in JavaScript. It gets compiled to React.createElement() calls, making your code more readable and maintainable.

JSX Basics

// JSX
const element = <h1>Hello, {name}!</h1>;

// Compiled to:
const element = React.createElement('h1', null, 'Hello, ', name, '!');

// JSX with attributes
const link = <a href="https://react.dev" target="_blank">React Docs</a>;

// JSX with expressions
const count = 5;
const message = <p>You have {count} new {count === 1 ? 'message' : 'messages'}</p>;

// JSX with inline styles
const box = (
  <div style={{ backgroundColor: 'blue', padding: '10px', color: 'white' }}>
    Styled Box
  </div>
);

JSX Rules

  • Return a single root element (or use Fragment)

  • Close all tags (including self-closing like <img />)

  • Use camelCase for attributes (className instead of class)

// ❌ Wrong - multiple root elements
function Wrong() {
  return (
    <h1>Title</h1>
    <p>Content</p>
  );
}

// ✅ Correct - single root element
function Correct() {
  return (
    <div>
      <h1>Title</h1>
      <p>Content</p>
    </div>
  );
}

// ✅ Correct - using Fragment
function AlsoCorrect() {
  return (
    <>
      <h1>Title</h1>
      <p>Content</p>
    </>
  );
}

Props - Component Configuration

Props (properties) are read-only data passed from parent to child components. They allow components to be configurable and reusable.

Basic Props

// Parent component
function App() {
  return <Greeting name="John" age={25} isAdmin={true} />;
}

// Child component with destructured props
function Greeting({ name, age, isAdmin }) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>You are {age} years old.</p>
      {isAdmin && <span className="badge">Admin</span>}
    </div>
  );
}

Default Props & PropTypes

// Default props with ES6 default parameters
function Button({ text = 'Click me', variant = 'primary', onClick }) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {text}
    </button>
  );
}

// With TypeScript
interface ButtonProps {
  text?: string;
  variant?: 'primary' | 'secondary' | 'danger';
  onClick?: () => void;
  disabled?: boolean;
}

function TypedButton({
  text = 'Click me',
  variant = 'primary',
  onClick,
  disabled = false,
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
      disabled={disabled}
    >
      {text}
    </button>
  );
}

Children Prop

// children prop allows nesting content
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
    </div>
  );
}

// Usage
function App() {
  return (
    <Card title="User Profile">
      <p>Name: John Doe</p>
      <p>Email: john@example.com</p>
      <button>Edit Profile</button>
    </Card>
  );
}

State - Managing Component Data

State is data that can change over time within a component. When state changes, React re-renders the component to reflect the new data. State is local to the component and cannot be accessed by other components unless passed as props.

useState Hook

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

// State with object
function UserForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: 0,
  });
  
  const handleChange = (field, value) => {
    setFormData(prev => ({ ...prev, [field]: value }));
  };
  
  return (
    <form>
      <input
        value={formData.name}
        onChange={(e) => handleChange('name', e.target.value)}
        placeholder="Name"
      />
      <input
        value={formData.email}
        onChange={(e) => handleChange('email', e.target.value)}
        placeholder="Email"
        type="email"
      />
      <input
        value={formData.age}
        onChange={(e) => handleChange('age', parseInt(e.target.value))}
        placeholder="Age"
        type="number"
      />
    </form>
  );
}

State vs Props

  • Props: Read-only, passed from parent, used to configure component

  • State: Mutable, managed by component, changes trigger re-renders

  • Props flow down, events flow up

Virtual DOM - React's Secret Weapon

The Virtual DOM is a lightweight JavaScript representation of the real DOM. React uses it to optimize rendering by calculating the minimal number of changes needed and batching DOM updates.

How it Works

  • When state changes, React creates a new Virtual DOM tree

  • React compares (diffs) the new tree with the previous tree

  • React calculates the minimal set of changes needed

  • React batches and applies those changes to the real DOM

Reconciliation Process

Reconciliation is the process by which React updates the DOM. When a component's state or props change, React uses a diffing algorithm to determine what changed.

// Example: List rendering with keys
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        // Key helps React identify which items changed
        <li key={todo.id}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

// ❌ Bad - using index as key (can cause bugs)
function BadList({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li> // Don't do this!
      ))}
    </ul>
  );
}

// ✅ Good - using unique ID
function GoodList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  );
}

Component Lifecycle

React components go through different phases during their lifetime. In functional components, we manage lifecycle with useEffect hook.

Lifecycle Phases

  • Mounting: Component is created and inserted into the DOM

  • Updating: Component re-renders due to state or props changes

  • Unmounting: Component is removed from the DOM

Lifecycle with useEffect

import { useState, useEffect } from 'react';

function DataFetcher({ userId }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // ComponentDidMount + ComponentDidUpdate equivalent
  useEffect(() => {
    console.log('Effect running - component mounted or userId changed');
    
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
    
    // ComponentWillUnmount equivalent (cleanup)
    return () => {
      console.log('Cleanup - component will unmount or before next effect');
    };
  }, [userId]); // Dependencies - effect runs when userId changes
  
  if (loading) return <div>Loading...</div>;
  return <div>{data?.name}</div>;
}

Events and Event Handling

React handles events using camelCase syntax and passes synthetic event objects for cross-browser compatibility.

function EventExamples() {
  const [text, setText] = useState('');
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
  
  // Click handler
  const handleClick = (e) => {
    e.preventDefault();
    console.log('Button clicked');
  };
  
  // Input change handler
  const handleChange = (e) => {
    setText(e.target.value);
  };
  
  // Mouse move handler
  const handleMouseMove = (e) => {
    setMousePos({ x: e.clientX, y: e.clientY });
  };
  
  // Form submit handler
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted with:', text);
  };
  
  return (
    <div onMouseMove={handleMouseMove}>
      <button onClick={handleClick}>Click Me</button>
      
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={text}
          onChange={handleChange}
          placeholder="Type something"
        />
        <button type="submit">Submit</button>
      </form>
      
      <p>Mouse: {mousePos.x}, {mousePos.y}</p>
    </div>
  );
}

Conditional Rendering

React provides multiple ways to conditionally render components based on state or props.

// If-else with return
function UserGreeting({ user }) {
  if (!user) {
    return <h1>Please sign in</h1>;
  }
  
  return <h1>Welcome, {user.name}!</h1>;
}

// Ternary operator
function Status({ isOnline }) {
  return (
    <div>
      Status: {isOnline ? '🟢 Online' : '🔴 Offline'}
    </div>
  );
}

// Logical && operator
function Notifications({ count }) {
  return (
    <div>
      <h2>Notifications</h2>
      {count > 0 && (
        <span className="badge">{count} new</span>
      )}
    </div>
  );
}

// Switch-case with object mapping
function StatusIcon({ status }) {
  const icons = {
    success: '✅',
    warning: '⚠️',
    error: '❌',
    info: 'ℹ️',
  };
  
  return <span>{icons[status] || '❓'}</span>;
}

Lists and Keys

When rendering lists, React needs keys to identify which items have changed, been added, or removed. Keys should be stable, unique among siblings, and not change between renders.

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </li>
      ))}
    </ul>
  );
}

// Complex list with nested data
function PostList({ posts }) {
  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
          <div className="comments">
            {post.comments.map(comment => (
              <div key={comment.id}>
                <strong>{comment.author}:</strong> {comment.text}
              </div>
            ))}
          </div>
        </article>
      ))}
    </div>
  );
}
React

Testing Strategies

React Testing Strategies Comprehensive guide to testing React applications - from unit tests to end-to-end testing, covering tools, best practices, and real-wor

React Testing Strategies

Comprehensive guide to testing React applications - from unit tests to end-to-end testing, covering tools, best practices, and real-world examples:

Testing Philosophy

The testing pyramid for React applications consists of three layers: Unit Tests (70%), Integration Tests (20%), and End-to-End Tests (10%). Focus on testing behavior, not implementation details.

Jest - The Testing Framework

Jest is a delightful JavaScript testing framework with a focus on simplicity. It works out of the box with React and provides mocking, assertions, and code coverage.

Basic Jest Test

// sum.js
export function sum(a, b) {
  return a + b;
}

// sum.test.js
import { sum } from './sum';

describe('sum function', () => {
  it('adds two numbers correctly', () => {
    expect(sum(1, 2)).toBe(3);
  });
  
  it('handles negative numbers', () => {
    expect(sum(-1, 1)).toBe(0);
  });
});

Mocking with Jest

// Mock API calls
jest.mock('./api');

import { fetchUser } from './api';

test('fetches user data', async () => {
  fetchUser.mockResolvedValue({
    id: 1,
    name: 'John Doe',
    email: 'john@example.com'
  });
  
  const user = await fetchUser(1);
  expect(user.name).toBe('John Doe');
  expect(fetchUser).toHaveBeenCalledWith(1);
});

React Testing Library

React Testing Library builds on top of DOM Testing Library by adding APIs for working with React components. It encourages testing from a user's perspective.

Basic Component Test

import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

describe('Counter Component', () => {
  it('renders with initial count', () => {
    render(<Counter initialCount={0} />);
    expect(screen.getByText(/count: 0/i)).toBeInTheDocument();
  });
  
  it('increments count when button is clicked', () => {
    render(<Counter initialCount={0} />);
    const button = screen.getByRole('button', { name: /increment/i });
    
    fireEvent.click(button);
    expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
    
    fireEvent.click(button);
    expect(screen.getByText(/count: 2/i)).toBeInTheDocument();
  });
});

Testing Async Components

import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';

test('loads and displays user data', async () => {
  // Mock API
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ name: 'John', email: 'john@example.com' }),
    })
  );
  
  render(<UserProfile userId={1} />);
  
  // Initially shows loading
  expect(screen.getByText(/loading/i)).toBeInTheDocument();
  
  // Wait for data to load
  await waitFor(() => {
    expect(screen.getByText('John')).toBeInTheDocument();
  });
  
  expect(screen.getByText('john@example.com')).toBeInTheDocument();
});

Testing User Interactions

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LoginForm from './LoginForm';

test('submits form with user credentials', async () => {
  const user = userEvent.setup();
  const handleSubmit = jest.fn();
  
  render(<LoginForm onSubmit={handleSubmit} />);
  
  // Type in email and password
  await user.type(screen.getByLabelText(/email/i), 'test@example.com');
  await user.type(screen.getByLabelText(/password/i), 'password123');
  
  // Click submit button
  await user.click(screen.getByRole('button', { name: /submit/i }));
  
  // Verify form submission
  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123',
  });
});

Testing Hooks

Use @testing-library/react-hooks to test custom hooks in isolation without rendering components.

import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';

test('useCounter hook', () => {
  const { result } = renderHook(() => useCounter(0));
  
  // Initial state
  expect(result.current.count).toBe(0);
  
  // Increment
  act(() => {
    result.current.increment();
  });
  expect(result.current.count).toBe(1);
  
  // Decrement
  act(() => {
    result.current.decrement();
  });
  expect(result.current.count).toBe(0);
  
  // Reset
  act(() => {
    result.current.increment();
    result.current.increment();
    result.current.reset();
  });
  expect(result.current.count).toBe(0);
});

Testing Context and Providers

When testing components that use Context, wrap them in the appropriate provider or create a custom render function.

import { render, screen } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import ThemedButton from './ThemedButton';

// Custom render with providers
function renderWithProviders(ui, options = {}) {
  const { theme = 'light', ...renderOptions } = options;
  
  return render(
    <ThemeProvider initialTheme={theme}>
      {ui}
    </ThemeProvider>,
    renderOptions
  );
}

test('button uses theme context', () => {
  renderWithProviders(<ThemedButton>Click Me</ThemedButton>, {
    theme: 'dark'
  });
  
  const button = screen.getByRole('button');
  expect(button).toHaveClass('dark-theme');
});

Integration Testing

Integration tests verify that multiple components work together correctly. They test component interactions, data flow, and complex user scenarios.

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TodoApp from './TodoApp';

test('complete todo workflow', async () => {
  const user = userEvent.setup();
  render(<TodoApp />);
  
  // Add a todo
  const input = screen.getByPlaceholderText(/add todo/i);
  await user.type(input, 'Buy groceries');
  await user.click(screen.getByRole('button', { name: /add/i }));
  
  // Verify todo appears
  expect(screen.getByText('Buy groceries')).toBeInTheDocument();
  
  // Toggle todo completion
  const checkbox = screen.getByRole('checkbox', { name: /buy groceries/i });
  await user.click(checkbox);
  
  // Verify todo is marked complete
  expect(checkbox).toBeChecked();
  
  // Filter to show only completed
  await user.click(screen.getByRole('button', { name: /completed/i }));
  expect(screen.getByText('Buy groceries')).toBeInTheDocument();
  
  // Filter to show only active
  await user.click(screen.getByRole('button', { name: /active/i }));
  expect(screen.queryByText('Buy groceries')).not.toBeInTheDocument();
});

End-to-End Testing with Cypress

Cypress provides a complete end-to-end testing solution that runs in the browser and provides excellent debugging capabilities.

// cypress/e2e/todo-app.cy.js
describe('Todo App', () => {
  beforeEach(() => {
    cy.visit('http://localhost:3000');
  });
  
  it('allows users to add and complete todos', () => {
    // Add a todo
    cy.get('[data-testid="todo-input"]').type('Buy groceries');
    cy.get('[data-testid="add-button"]').click();
    
    // Verify todo appears
    cy.contains('Buy groceries').should('be.visible');
    
    // Complete the todo
    cy.get('[data-testid="todo-checkbox"]').first().click();
    cy.get('[data-testid="todo-item"]').first().should('have.class', 'completed');
  });
  
  it('persists todos after page reload', () => {
    // Add todo
    cy.get('[data-testid="todo-input"]').type('Test persistence');
    cy.get('[data-testid="add-button"]').click();
    
    // Reload page
    cy.reload();
    
    // Verify todo still exists
    cy.contains('Test persistence').should('be.visible');
  });
  
  it('filters todos correctly', () => {
    // Add multiple todos
    ['Todo 1', 'Todo 2', 'Todo 3'].forEach(todo => {
      cy.get('[data-testid="todo-input"]').type(todo);
      cy.get('[data-testid="add-button"]').click();
    });
    
    // Complete first todo
    cy.get('[data-testid="todo-checkbox"]').first().click();
    
    // Filter to show only completed
    cy.get('[data-testid="filter-completed"]').click();
    cy.get('[data-testid="todo-item"]').should('have.length', 1);
    cy.contains('Todo 1').should('be.visible');
    
    // Filter to show only active
    cy.get('[data-testid="filter-active"]').click();
    cy.get('[data-testid="todo-item"]').should('have.length', 2);
  });
});

End-to-End Testing with Playwright

Playwright enables reliable end-to-end testing for modern web apps with support for all browsers and provides powerful auto-waiting and debugging features.

import { test, expect } from '@playwright/test';

test.describe('Todo Application', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('http://localhost:3000');
  });
  
  test('should add and complete a todo', async ({ page }) => {
    // Add a new todo
    await page.fill('[data-testid="todo-input"]', 'Buy groceries');
    await page.click('[data-testid="add-button"]');
    
    // Verify todo is added
    await expect(page.locator('text=Buy groceries')).toBeVisible();
    
    // Complete the todo
    await page.click('[data-testid="todo-checkbox"]');
    
    // Verify completed state
    await expect(page.locator('[data-testid="todo-item"]')).toHaveClass(/completed/);
  });
  
  test('should filter todos', async ({ page }) => {
    // Add multiple todos
    const todos = ['Todo 1', 'Todo 2', 'Todo 3'];
    
    for (const todo of todos) {
      await page.fill('[data-testid="todo-input"]', todo);
      await page.click('[data-testid="add-button"]');
    }
    
    // Complete first todo
    await page.locator('[data-testid="todo-checkbox"]').first().click();
    
    // Filter completed
    await page.click('text=Completed');
    await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1);
    
    // Filter active
    await page.click('text=Active');
    await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(2);
  });
  
  test('should handle network errors gracefully', async ({ page }) => {
    // Intercept and fail API request
    await page.route('**/api/todos', route => route.abort());
    
    await page.fill('[data-testid="todo-input"]', 'Test error');
    await page.click('[data-testid="add-button"]');
    
    // Verify error message appears
    await expect(page.locator('text=Failed to add todo')).toBeVisible();
  });
});

Testing Best Practices

  • Test behavior, not implementation: Focus on what users see and do

  • Use semantic queries: getByRole, getByLabelText over getByTestId

  • Avoid testing implementation details: Don't test state or internal methods

  • Mock external dependencies: APIs, timers, external libraries

  • Write descriptive test names: Describe what the test does and expects

  • Keep tests isolated: Each test should be independent

Snapshot Testing

Snapshot tests are useful for catching unexpected UI changes, but should be used sparingly and reviewed carefully.

import { render } from '@testing-library/react';
import Button from './Button';

test('Button snapshot', () => {
  const { container } = render(
    <Button variant="primary" size="large">
      Click Me
    </Button>
  );
  
  expect(container.firstChild).toMatchSnapshot();
});

// Update snapshots with: jest -u

Code Coverage

Measure and track code coverage to ensure your tests cover critical paths. Aim for 80%+ coverage on business logic.

# Run tests with coverage
npm test -- --coverage

# Generate HTML coverage report
npm test -- --coverage --coverageReporters=html

# Coverage thresholds in package.json
{
  "jest": {
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

Visual Regression Testing

Catch visual bugs by comparing screenshots before and after changes using tools like Percy or Chromatic.

// Percy with Cypress
import '@percy/cypress';

it('matches button snapshot', () => {
  cy.visit('/components/button');
  cy.percySnapshot('Button Component');
});

// Chromatic with Storybook
// chromatic.yml
name: Chromatic

on: push

jobs:
  chromatic:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: chromaui/action@v1
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
React

Common Patterns

React Common Patterns Essential React patterns and design principles that help you write clean, maintainable, and scalable code: Compound Components Pattern Com

React Common Patterns

Essential React patterns and design principles that help you write clean, maintainable, and scalable code:

Compound Components Pattern

Compound components are components that work together to form a complete UI. They share an implicit state and communicate with each other, providing a flexible and expressive API.

import { createContext, useContext, useState } from 'react';

// Context for sharing state
const TabsContext = createContext();

// Parent component
function Tabs({ children, defaultValue }) {
  const [activeTab, setActiveTab] = useState(defaultValue);
  
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

// Tab list component
function TabList({ children }) {
  return <div className="tab-list">{children}</div>;
}

// Individual tab
function Tab({ value, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  const isActive = activeTab === value;
  
  return (
    <button
      onClick={() => setActiveTab(value)}
      className={isActive ? 'tab active' : 'tab'}
    >
      {children}
    </button>
  );
}

// Tab panel
function TabPanel({ value, children }) {
  const { activeTab } = useContext(TabsContext);
  
  if (activeTab !== value) return null;
  
  return <div className="tab-panel">{children}</div>;
}

// Usage - expressive and flexible API
function App() {
  return (
    <Tabs defaultValue="account">
      <TabList>
        <Tab value="account">Account</Tab>
        <Tab value="settings">Settings</Tab>
        <Tab value="notifications">Notifications</Tab>
      </TabList>
      
      <TabPanel value="account">
        <AccountSettings />
      </TabPanel>
      <TabPanel value="settings">
        <GeneralSettings />
      </TabPanel>
      <TabPanel value="notifications">
        <NotificationSettings />
      </TabPanel>
    </Tabs>
  );
}

// Export as compound component
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

Render Props Pattern

Render props is a technique for sharing code between components using a prop whose value is a function. It allows you to pass rendering logic from parent to child.

// Mouse tracker component using render props
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMouseMove = (event) => {
      setPosition({ x: event.clientX, y: event.clientY });
    };
    
    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, []);
  
  return render(position);
}

// Data fetcher with render props
function DataFetcher({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [url]);
  
  return render({ data, loading, error });
}

// Usage
function App() {
  return (
    <div>
      {/* Mouse position display */}
      <MouseTracker
        render={({ x, y }) => (
          <div>Mouse position: {x}, {y}</div>
        )}
      />
      
      {/* User data display */}
      <DataFetcher
        url="/api/user"
        render={({ data, loading, error }) => {
          if (loading) return <div>Loading...</div>;
          if (error) return <div>Error: {error.message}</div>;
          return <div>User: {data.name}</div>;
        }}
      />
    </div>
  );
}

Higher-Order Components (HOCs)

HOCs are functions that take a component and return a new enhanced component. They are used to reuse component logic across multiple components.

// withAuth HOC - adds authentication logic
function withAuth(WrappedComponent) {
  return function AuthComponent(props) {
    const { user, loading } = useAuth();
    
    if (loading) {
      return <div>Loading...</div>;
    }
    
    if (!user) {
      return <Navigate to="/login" />;
    }
    
    return <WrappedComponent {...props} user={user} />;
  };
}

// withLogging HOC - adds logging
function withLogging(WrappedComponent, componentName) {
  return function LoggingComponent(props) {
    useEffect(() => {
      console.log(`${componentName} mounted`);
      return () => console.log(`${componentName} unmounted`);
    }, []);
    
    return <WrappedComponent {...props} />;
  };
}

// withData HOC - adds data fetching
function withData(WrappedComponent, fetchData) {
  return function DataComponent(props) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
      fetchData(props)
        .then(data => {
          setData(data);
          setLoading(false);
        })
        .catch(err => {
          setError(err);
          setLoading(false);
        });
    }, [props.id]); // Refetch when ID changes
    
    return <WrappedComponent {...props} data={data} loading={loading} error={error} />;
  };
}

// Usage - compose multiple HOCs
const UserProfile = ({ user, data }) => (
  <div>
    <h1>{user.name}</h1>
    <p>{data.bio}</p>
  </div>
);

const EnhancedUserProfile = withAuth(
  withLogging(
    withData(UserProfile, ({ id }) => fetch(`/api/users/${id}`)),
    'UserProfile'
  )
);

Container/Presentational Pattern

Separate components into containers (logic) and presentational (UI) components for better reusability and testability.

// Presentational component - pure UI
function UserCard({ name, email, avatar, onEdit, onDelete }) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
      <div>
        <button onClick={onEdit}>Edit</button>
        <button onClick={onDelete}>Delete</button>
      </div>
    </div>
  );
}

// Container component - handles logic and state
function UserCardContainer({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchUser(userId)
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]);
  
  const handleEdit = () => {
    // Edit logic
    console.log('Edit user:', userId);
  };
  
  const handleDelete = async () => {
    await deleteUser(userId);
    // Update state or navigate
  };
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <UserCard
      name={user.name}
      email={user.email}
      avatar={user.avatar}
      onEdit={handleEdit}
      onDelete={handleDelete}
    />
  );
}

Custom Hooks Pattern

Custom hooks extract reusable logic from components. They follow the naming convention of starting with "use" and can call other hooks.

// useToggle - manage boolean state
function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  
  const toggle = useCallback(() => setValue(v => !v), []);
  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);
  
  return [value, { toggle, setTrue, setFalse }];
}

// useDebounce - debounce a value
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(handler);
  }, [value, delay]);
  
  return debouncedValue;
}

// useMediaQuery - responsive design
function useMediaQuery(query) {
  const [matches, setMatches] = useState(false);
  
  useEffect(() => {
    const media = window.matchMedia(query);
    setMatches(media.matches);
    
    const listener = (e) => setMatches(e.matches);
    media.addEventListener('change', listener);
    
    return () => media.removeEventListener('change', listener);
  }, [query]);
  
  return matches;
}

// Usage
function SearchInput() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
  const isMobile = useMediaQuery('(max-width: 768px)');
  const [isOpen, { toggle, setFalse }] = useToggle();
  
  useEffect(() => {
    if (debouncedSearchTerm) {
      // Perform search
      console.log('Searching for:', debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);
  
  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder={isMobile ? 'Search...' : 'Search for anything...'}
      />
      <button onClick={toggle}>
        {isOpen ? 'Close Filters' : 'Open Filters'}
      </button>
      {isOpen && <FilterPanel onClose={setFalse} />}
    </div>
  );
}

Provider Pattern

The Provider pattern uses React Context to provide data to deeply nested components without prop drilling.

import { createContext, useContext, useState } from 'react';

// Create context
const AuthContext = createContext();

// Provider component
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Check if user is logged in
    checkAuth()
      .then(userData => {
        setUser(userData);
        setLoading(false);
      })
      .catch(() => {
        setUser(null);
        setLoading(false);
      });
  }, []);
  
  const login = async (credentials) => {
    const userData = await authenticate(credentials);
    setUser(userData);
    localStorage.setItem('token', userData.token);
  };
  
  const logout = () => {
    setUser(null);
    localStorage.removeItem('token');
  };
  
  const value = {
    user,
    loading,
    login,
    logout,
    isAuthenticated: !!user,
  };
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

// Custom hook for consuming context
export function useAuth() {
  const context = useContext(AuthContext);
  
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  
  return context;
}

// Usage in app
function App() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
        </Routes>
      </Router>
    </AuthProvider>
  );
}

// Protected route component
function ProtectedRoute({ children }) {
  const { isAuthenticated, loading } = useAuth();
  
  if (loading) return <div>Loading...</div>;
  
  return isAuthenticated ? children : <Navigate to="/login" />;
}

Controlled vs Uncontrolled Components

Controlled components have their state controlled by React, while uncontrolled components manage their own state internally.

// Controlled component - React controls the state
function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Controlled input"
    />
  );
}

// Uncontrolled component - DOM controls the state
function UncontrolledInput() {
  const inputRef = useRef();
  
  const handleSubmit = () => {
    console.log('Input value:', inputRef.current.value);
  };
  
  return (
    <div>
      <input
        ref={inputRef}
        defaultValue=""
        placeholder="Uncontrolled input"
      />
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
}

// Hybrid approach - controlled with default value
function HybridInput({ defaultValue, onChange }) {
  const [value, setValue] = useState(defaultValue);
  
  const handleChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
    onChange?.(newValue);
  };
  
  return (
    <input
      value={value}
      onChange={handleChange}
      placeholder="Hybrid input"
    />
  );
}

Prop Drilling Solution - Composition

Instead of passing props through many layers, use component composition to pass components directly where they're needed.

// ❌ Prop drilling - passing props through many layers
function App() {
  const user = useUser();
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Sidebar user={user} />;
}

function Sidebar({ user }) {
  return <UserMenu user={user} />;
}

function UserMenu({ user }) {
  return <div>{user.name}</div>;
}

// ✅ Component composition - pass components as props
function App() {
  const user = useUser();
  
  return (
    <Layout
      sidebar={<Sidebar userMenu={<UserMenu user={user} />} />}
    >
      <MainContent />
    </Layout>
  );
}

function Layout({ sidebar, children }) {
  return (
    <div className="layout">
      <div className="sidebar">{sidebar}</div>
      <div className="main">{children}</div>
    </div>
  );
}

function Sidebar({ userMenu }) {
  return (
    <div>
      <Navigation />
      {userMenu}
    </div>
  );
}

Lazy Loading and Code Splitting

Load components on demand to reduce initial bundle size and improve performance.

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Profile = lazy(() => import('./pages/Profile'));

// Loading fallback component
function PageLoader() {
  return (
    <div className="page-loader">
      <div className="spinner" />
      <p>Loading...</p>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="/profile" element={<Profile />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// Lazy load heavy components
const HeavyChart = lazy(() => import('./components/HeavyChart'));

function Analytics() {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowChart(true)}>Show Chart</button>
      
      {showChart && (
        <Suspense fallback={<div>Loading chart...</div>}>
          <HeavyChart data={analyticsData} />
        </Suspense>
      )}
    </div>
  );
}

Error Boundaries

Error boundaries catch JavaScript errors anywhere in their child component tree and display a fallback UI instead of crashing.

import { Component } from 'react';

// Error boundary class component
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // Log to error reporting service
    logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h1>Something went wrong</h1>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try again
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route
            path="/dashboard"
            element={
              <ErrorBoundary>
                <Dashboard />
              </ErrorBoundary>
            }
          />
        </Routes>
      </Router>
    </ErrorBoundary>
  );
}

Portal Pattern

Portals provide a way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

import { createPortal } from 'react-dom';

// Modal component using portal
function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;
  
  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <button className="modal-close" onClick={onClose}>×</button>
        {children}
      </div>
    </div>,
    document.body // Render to body instead of parent
  );
}

// Tooltip using portal
function Tooltip({ children, content, position }) {
  const [isVisible, setIsVisible] = useState(false);
  const [coords, setCoords] = useState({ x: 0, y: 0 });
  const triggerRef = useRef();
  
  const showTooltip = () => {
    const rect = triggerRef.current.getBoundingClientRect();
    setCoords({ x: rect.left, y: rect.bottom + 5 });
    setIsVisible(true);
  };
  
  return (
    <>
      <span
        ref={triggerRef}
        onMouseEnter={showTooltip}
        onMouseLeave={() => setIsVisible(false)}
      >
        {children}
      </span>
      
      {isVisible && createPortal(
        <div
          className="tooltip"
          style={{ position: 'absolute', left: coords.x, top: coords.y }}
        >
          {content}
        </div>,
        document.body
      )}
    </>
  );
}

// Usage
function App() {
  const [showModal, setShowModal] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowModal(true)}>Open Modal</button>
      
      <Modal isOpen={showModal} onClose={() => setShowModal(false)}>
        <h2>Modal Title</h2>
        <p>Modal content here...</p>
      </Modal>
      
      <Tooltip content="Helpful information">
        <span>Hover me</span>
      </Tooltip>
    </div>
  );
}

Keep your React knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever