Skip to content

React

React is a JavaScript library for building user interfaces through reusable components. This cheat sheet covers React 18+ with modern hooks, patterns, and best practices.

Quick Start

Installation

# Create new React app with Vite (recommended)
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev

# Create React App (Legacy)
npx create-react-app my-app

# Add React to existing project
npm install react react-dom

Basic Setup

// index.js - Entry point
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Core Concepts

JSX Fundamentals

// JSX allows HTML-like syntax in JavaScript
function Welcome({ name }) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Welcome to React</p>
    </div>
  );
}

// JSX expressions
const user = { name: 'Alice', age: 30 };
const element = (
  <div>
    <p>Name: {user.name}</p>
    <p>Age: {user.age}</p>
    <p>Status: {user.age >= 18 ? 'Adult' : 'Minor'}</p>
  </div>
);

// JSX attributes
const imageUrl = 'https://example.com/image.jpg';
const image = <img src={imageUrl} alt="Description" className="image-style" />;

Components

// Basic function component
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// Arrow function component
const Greeting = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

// Component with multiple props
function UserCard({ user, showEmail = false }) {
  return (
    <div className="user-card">
      <h2>{user.name}</h2>
      <p>Age: {user.age}</p>
      {showEmail && <p>Email: {user.email}</p>}
    </div>
  );
}

Class Components (Legacy)

import React, { Component } from 'react';

class Welcome extends Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

State Management

useState Hook

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(prev => prev - 1); // Functional update

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

// Multiple state variables
function UserProfile() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  return (
    <form>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <input 
        value={email} 
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
    </form>
  );
}

// Object state
function UserSettings() {
  const [settings, setSettings] = useState({
    theme: 'light',
    notifications: true,
    language: 'en'
  });

  const updateTheme = (theme) => {
    setSettings(prev => ({ ...prev, theme }));
  };

  return (
    <div>
      <button onClick={() => updateTheme('dark')}>Dark Theme</button>
      <button onClick={() => updateTheme('light')}>Light Theme</button>
    </div>
  );
}

useReducer Hook

import { useReducer } from 'react';

// Reducer function
function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

// Complex state management
const todoReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.text, done: false }];
    case 'TOGGLE_TODO':
      return state.map(todo => 
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
};

Effects and Side Effects

useEffect Hook

import { useEffect, useState } from 'react';

// Basic effect (runs after every render)
function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Count: ${count}`;
  });

  return <div>Count: {count}</div>;
}

// Effect with dependency array
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // Only re-run when userId changes

  return user ? <div>{user.name}</div> : <div>Loading...</div>;
}

// Effect with cleanup
function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    // Cleanup function
    return () => clearInterval(interval);
  }, []); // Empty dependency array = run once

  return <div>Seconds: {seconds}</div>;
}

// Multiple effects
function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
  const [isConnected, setIsConnected] = useState(false);

  // Effect for connection
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    setIsConnected(true);

    return () => {
      connection.disconnect();
      setIsConnected(false);
    };
  }, [roomId]);

  // Effect for messages
  useEffect(() => {
    if (isConnected) {
      const unsubscribe = subscribeToMessages(roomId, setMessages);
      return unsubscribe;
    }
  }, [roomId, isConnected]);
}

Event Handling

Common Event Patterns

function Form() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });

  // Handle input changes
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  // Handle form submission
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
    // Reset form
    setFormData({ name: '', email: '', message: '' });
  };

  // Handle button clicks
  const handleButtonClick = (e) => {
    console.log('Button clicked', e.target);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Name"
      />
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <textarea
        name="message"
        value={formData.message}
        onChange={handleChange}
        placeholder="Message"
      />
      <button type="submit">Submit</button>
      <button type="button" onClick={handleButtonClick}>Cancel</button>
    </form>
  );
}

// Event delegation and synthetic events
function ItemList({ items }) {
  const handleItemClick = (e, itemId) => {
    e.stopPropagation(); // Prevent event bubbling
    console.log(`Clicked item ${itemId}`);
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={(e) => handleItemClick(e, item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

Props and Communication

Props Patterns

// Basic props
function Greeting({ name, age = 0 }) { // Default props
  return <p>Hello {name}, age {age}</p>;
}

// Destructuring props
function UserCard({ user: { name, email, avatar } }) {
  return (
    <div>
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
}

// Spread props
function Button({ children, ...props }) {
  return (
    <button {...props} className={`btn ${props.className || ''}`}>
      {children}
    </button>
  );
}

// Render props pattern
function DataFetcher({ render, url }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return render({ data, loading });
}

// Usage
<DataFetcher 
  url="/api/users" 
  render={({ data, loading }) => 
    loading ? <div>Loading...</div> : <UserList users={data} />
  }
/>

// Children prop patterns
function Card({ children, title, footer }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-content">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// Higher-Order Component pattern
function withLoading(WrappedComponent) {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) return <div>Loading...</div>;
    return <WrappedComponent {...props} />;
  };
}

Lifting State Up

function ParentComponent() {
  const [sharedState, setSharedState] = useState('');

  return (
    <div>
      <ChildA value={sharedState} onChange={setSharedState} />
      <ChildB value={sharedState} />
    </div>
  );
}

function ChildA({ value, onChange }) {
  return (
    <input 
      value={value} 
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

function ChildB({ value }) {
  return <p>Current value: {value}</p>;
}

Context API

Creating and Using Context

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

// Create context
const ThemeContext = createContext();

// Provider component
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

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

// Using context in components
function ThemedButton() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button 
      className={`btn btn-${theme}`}
      onClick={toggleTheme}
    >
      Toggle Theme
    </button>
  );
}

// App with context
function App() {
  return (
    <ThemeProvider>
      <div className="app">
        <ThemedButton />
      </div>
    </ThemeProvider>
  );
}

Complex Context with Reducer

// Auth context with reducer
const AuthContext = createContext();

const authReducer = (state, action) => {
  switch (action.type) {
    case 'LOGIN':
      return { ...state, user: action.user, isAuthenticated: true };
    case 'LOGOUT':
      return { ...state, user: null, isAuthenticated: false };
    case 'SET_LOADING':
      return { ...state, isLoading: action.isLoading };
    default:
      return state;
  }
};

function AuthProvider({ children }) {
  const [state, dispatch] = useReducer(authReducer, {
    user: null,
    isAuthenticated: false,
    isLoading: true
  });

  const login = async (credentials) => {
    dispatch({ type: 'SET_LOADING', isLoading: true });
    try {
      const user = await authService.login(credentials);
      dispatch({ type: 'LOGIN', user });
    } catch (error) {
      console.error('Login failed:', error);
    } finally {
      dispatch({ type: 'SET_LOADING', isLoading: false });
    }
  };

  return (
    <AuthContext.Provider value={{ ...state, login, dispatch }}>
      {children}
    </AuthContext.Provider>
  );
}

Custom Hooks

Creating Custom Hooks

// Custom hook for API calls
function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// Custom hook for local storage
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];
}

// Custom hook for form handling
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
  };

  const handleSubmit = (callback) => (e) => {
    e.preventDefault();
    callback(values);
  };

  const reset = () => {
    setValues(initialValues);
    setErrors({});
  };

  return {
    values,
    errors,
    handleChange,
    handleSubmit,
    reset,
    setErrors
  };
}

// Usage
function ContactForm() {
  const { values, handleChange, handleSubmit, reset } = useForm({
    name: '',
    email: '',
    message: ''
  });

  const onSubmit = (formData) => {
    console.log('Submitting:', formData);
    reset();
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        name="name"
        value={values.name}
        onChange={handleChange}
        placeholder="Name"
      />
      {/* ... other inputs */}
    </form>
  );
}

Performance Optimization

React.memo

import { memo } from 'react';

// Memoized component - only re-renders if props change
const ExpensiveComponent = memo(function ExpensiveComponent({ data, onUpdate }) {
  return (
    <div>
      {data.map(item => (
        <div key={item.id}>
          <span>{item.name}</span>
          <button onClick={() => onUpdate(item.id)}>Update</button>
        </div>
      ))}
    </div>
  );
});

// Custom comparison function
const OptimizedComponent = memo(function OptimizedComponent({ user, settings }) {
  return <div>{user.name} - {settings.theme}</div>;
}, (prevProps, nextProps) => {
  return prevProps.user.id === nextProps.user.id &&
         prevProps.settings.theme === nextProps.settings.theme;
});

useMemo and useCallback

import { useMemo, useCallback, useState } from 'react';

function ExpensiveList({ items, filter }) {
  // Memoize expensive calculations
  const filteredItems = useMemo(() => {
    return items.filter(item => item.name.includes(filter));
  }, [items, filter]);

  const expensiveValue = useMemo(() => {
    return items.reduce((sum, item) => sum + item.price, 0);
  }, [items]);

  // Memoize callback functions
  const handleItemClick = useCallback((itemId) => {
    console.log(`Clicked item ${itemId}`);
  }, []);

  const handleSort = useCallback((sortBy) => {
    // Sort logic here
  }, []);

  return (
    <div>
      <p>Total: ${expensiveValue}</p>
      {filteredItems.map(item => (
        <ItemCard
          key={item.id}
          item={item}
          onClick={handleItemClick}
        />
      ))}
    </div>
  );
}

Conditional Rendering

Conditional Patterns

function ConditionalExample({ user, isLoggedIn, items = [] }) {
  return (
    <div>
      {/* Simple conditional */}
      {isLoggedIn && <p>Welcome, {user.name}!</p>}

      {/* Ternary operator */}
      {isLoggedIn ? (
        <UserDashboard user={user} />
      ) : (
        <LoginForm />
      )}

      {/* Complex conditions */}
      {isLoggedIn && user.role === 'admin' && (
        <AdminPanel />
      )}

      {/* Conditional with function */}
      {(() => {
        if (!isLoggedIn) return <LoginPrompt />;
        if (user.role === 'admin') return <AdminDashboard />;
        return <UserDashboard />;
      })()}

      {/* List rendering with conditions */}
      {items.length > 0 ? (
        <ul>
          {items.map(item => (
            <li key={item.id}>
              {item.name}
              {item.isNew && <span className="badge">New</span>}
            </li>
          ))}
        </ul>
      ) : (
        <p>No items found</p>
      )}
    </div>
  );
}

Lists and Keys

Rendering Lists

function ItemList({ items, onDelete, onEdit }) {
  return (
    <div>
      {items.map(item => (
        <div key={item.id} className="item">
          <h3>{item.title}</h3>
          <p>{item.description}</p>
          <button onClick={() => onEdit(item)}>Edit</button>
          <button onClick={() => onDelete(item.id)}>Delete</button>
        </div>
      ))}
    </div>
  );
}

// Dynamic list with state
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');

  const addTodo = () => {
    if (newTodo.trim()) {
      setTodos(prev => [...prev, {
        id: Date.now(),
        text: newTodo,
        completed: false
      }]);
      setNewTodo('');
    }
  };

  const toggleTodo = (id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  return (
    <div>
      <div>
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="Add todo..."
        />
        <button onClick={addTodo}>Add</button>
      </div>

      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none'
              }}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

Forms and Validation

Controlled Components

function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    category: 'general',
    message: '',
    subscribe: false
  });

  const [errors, setErrors] = useState({});

  const handleInputChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  const validateForm = () => {
    const newErrors = {};

    if (!formData.name.trim()) {
      newErrors.name = 'Name is required';
    }

    if (!formData.email.trim()) {
      newErrors.email = 'Email is required';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email is invalid';
    }

    if (!formData.message.trim()) {
      newErrors.message = 'Message is required';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validateForm()) {
      console.log('Form submitted:', formData);
      // Reset form
      setFormData({
        name: '',
        email: '',
        category: 'general',
        message: '',
        subscribe: false
      });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          Name:
          <input
            type="text"
            name="name"
            value={formData.name}
            onChange={handleInputChange}
          />
        </label>
        {errors.name && <span className="error">{errors.name}</span>}
      </div>

      <div>
        <label>
          Email:
          <input
            type="email"
            name="email"
            value={formData.email}
            onChange={handleInputChange}
          />
        </label>
        {errors.email && <span className="error">{errors.email}</span>}
      </div>

      <div>
        <label>
          Category:
          <select
            name="category"
            value={formData.category}
            onChange={handleInputChange}
          >
            <option value="general">General</option>
            <option value="support">Support</option>
            <option value="billing">Billing</option>
          </select>
        </label>
      </div>

      <div>
        <label>
          <input
            type="checkbox"
            name="subscribe"
            checked={formData.subscribe}
            onChange={handleInputChange}
          />
          Subscribe to newsletter
        </label>
      </div>

      <div>
        <label>
          Message:
          <textarea
            name="message"
            value={formData.message}
            onChange={handleInputChange}
            rows={4}
          />
        </label>
        {errors.message && <span className="error">{errors.message}</span>}
      </div>

      <button type="submit">Submit</button>
    </form>
  );
}

Error Boundaries

Error Boundary Component

import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error: error,
      errorInfo: errorInfo
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <details>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <Header />
      <MainContent />
      <Footer />
    </ErrorBoundary>
  );
}

Refs and DOM Access

useRef Hook

import { useRef, useEffect } from 'react';

function FocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // Focus input when component mounts
    inputRef.current.focus();
  }, []);

  const handleFocus = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus Input</button>
    </div>
  );
}

// Refs with state
function VideoPlayer({ src }) {
  const videoRef = useRef(null);
  const [isPlaying, setIsPlaying] = useState(false);

  const togglePlayPause = () => {
    const video = videoRef.current;
    if (isPlaying) {
      video.pause();
    } else {
      video.play();
    }
    setIsPlaying(!isPlaying);
  };

  return (
    <div>
      <video ref={videoRef} src={src} />
      <button onClick={togglePlayPause}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
    </div>
  );
}

// Forwarding refs
import { forwardRef } from 'react';

const CustomInput = forwardRef(function CustomInput(props, ref) {
  return <input {...props} ref={ref} className="custom-input" />;
});

// Usage of forwarded ref
function Parent() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={focusInput}>Focus</button>
    </div>
  );
}

Common Patterns and Best Practices

Component Composition

// Composition over inheritance
function Layout({ children }) {
  return (
    <div className="layout">
      <Header />
      <main>{children}</main>
      <Footer />
    </div>
  );
}

// Compound components
function Tabs({ children, defaultTab = 0 }) {
  const [activeTab, setActiveTab] = useState(defaultTab);

  return (
    <div className="tabs">
      {React.Children.map(children, (child, index) =>
        React.cloneElement(child, { activeTab, setActiveTab, index })
      )}
    </div>
  );
}

function TabList({ children, activeTab, setActiveTab }) {
  return (
    <div className="tab-list">
      {React.Children.map(children, (child, index) =>
        React.cloneElement(child, {
          isActive: activeTab === index,
          onClick: () => setActiveTab(index)
        })
      )}
    </div>
  );
}

// Usage
<Tabs defaultTab={0}>
  <TabList>
    <Tab>Tab 1</Tab>
    <Tab>Tab 2</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>Content 1</TabPanel>
    <TabPanel>Content 2</TabPanel>
  </TabPanels>
</Tabs>

Data Fetching Patterns

// Custom hook for data fetching with loading states
function useAsyncData(asyncFunction, dependencies = []) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isCancelled = false;

    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        const result = await asyncFunction();
        if (!isCancelled) {
          setData(result);
        }
      } catch (err) {
        if (!isCancelled) {
          setError(err);
        }
      } finally {
        if (!isCancelled) {
          setLoading(false);
        }
      }
    };

    fetchData();

    return () => {
      isCancelled = true;
    };
  }, dependencies);

  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data: user, loading, error } = useAsyncData(
    () => fetchUser(userId),
    [userId]
  );

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>User not found</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Testing Patterns

Component Testing Setup

// Example test with React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import Counter from './Counter';

test('renders counter and increments on click', async () => {
  render(<Counter />);

  // Find elements
  const countElement = screen.getByText(/count: 0/i);
  const incrementButton = screen.getByText(/increment/i);

  expect(countElement).toBeInTheDocument();

  // Simulate user interaction
  fireEvent.click(incrementButton);

  // Assert changes
  await waitFor(() => {
    expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
  });
});

// Testing with context
test('uses theme context correctly', () => {
  render(
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );

  const button = screen.getByRole('button');
  expect(button).toHaveClass('btn-light');

  fireEvent.click(button);
  expect(button).toHaveClass('btn-dark');
});

Common Gotchas and Best Practices

Key Best Practices

// ✅ Good: Stable keys for list items
items.map(item => <Item key={item.id} data={item} />)

// ❌ Bad: Using array index as key
items.map((item, index) => <Item key={index} data={item} />)

// ✅ Good: Functional state updates
setCount(prev => prev + 1);

// ❌ Bad: Direct state mutation
const newItems = items;
newItems.push(newItem);
setItems(newItems);

// ✅ Good: Immutable state updates
setItems(prev => [...prev, newItem]);

// ✅ Good: Effect cleanup
useEffect(() => {
  const subscription = subscribe();
  return () => subscription.unsubscribe();
}, []);

// ✅ Good: Dependency arrays
useEffect(() => {
  fetchData(userId);
}, [userId]); // Include all dependencies

// ❌ Bad: Missing dependencies
useEffect(() => {
  fetchData(userId);
}, []); // Missing userId dependency

Performance Tips

// Use React.memo for expensive components
const ExpensiveComponent = memo(({ data }) => {
  // Expensive rendering logic
  return <div>{/* Complex UI */}</div>;
});

// Use useMemo for expensive calculations
const expensiveValue = useMemo(() => {
  return data.reduce((acc, item) => acc + item.value, 0);
}, [data]);

// Use useCallback for stable function references
const handleClick = useCallback((id) => {
  onItemClick(id);
}, [onItemClick]);

// Lazy loading with React.lazy
const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

This cheat sheet covers the essential React concepts, patterns, and best practices for building modern React applications. Focus on understanding hooks, component patterns, and state management for effective React development.