Post

🌊 React.JS: Deep Dive & Best Practices

Comprehensive guide to mastering React from foundational concepts to advanced techniques and best practices!

🌊 React.JS: Deep Dive & Best Practices

React: Deep Dive & Best Practices


Table of Contents

  1. Introduction to React
  2. Core Concepts
  3. Component Lifecycle
  4. Hooks Deep Dive
  5. State Management
  6. Performance Optimization
  7. Best Practices
  8. Lifecycle Terminology Tables
  9. References

Introduction to React

React is a JavaScript library for building user interfaces, developed and maintained by Meta (formerly Facebook). It enables developers to create reusable UI components that efficiently update and render when data changes.

Key Characteristics

  • Declarative: Describe what the UI should look like based on current state, and React handles the updates
  • Component-Based: Build encapsulated components that manage their own state and compose them into complex UIs
  • Learn Once, Write Anywhere: Use React for web, mobile (React Native), desktop, and even VR applications

Why React?

React revolutionized frontend development by introducing:

  • Virtual DOM for efficient rendering
  • Unidirectional data flow for predictable state management
  • JSX syntax for intuitive component composition
  • Rich ecosystem with extensive tooling and libraries

React Trends Innovations React Trends & Innovations!


Core Concepts

1. JSX (JavaScript XML)

JSX is a syntax extension that allows writing HTML-like code within JavaScript. It gets transpiled to regular JavaScript function calls.

1
2
3
4
5
6
7
8
9
// JSX syntax
const element = <h1 className="greeting">Hello, World!</h1>;

// Transpiles to
const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, World!'
);

Key Rules:

  • Use className instead of class
  • Use htmlFor instead of for
  • All tags must be closed (self-closing for single tags: <img />)
  • JavaScript expressions go inside curly braces {}
  • Only one root element per component (use Fragments <>...</> if needed)

2. Components

Components are the building blocks of React applications. They encapsulate UI logic and can be reused throughout the application.

Functional Components

Modern React primarily uses functional components:

1
2
3
4
5
6
7
8
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// Arrow function variant
const Welcome = (props) => {
  return <h1>Hello, {props.name}</h1>;
};

Class Components (Legacy)

While less common today, understanding class components is valuable for maintaining legacy code:

1
2
3
4
5
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

3. Props (Properties)

Props are read-only inputs passed from parent to child components, enabling data flow and component reusability.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function UserCard(props) {
  return (
    <div className="user-card">
      <h2>{props.name}</h2>
      <p>{props.email}</p>
      <p>Age: {props.age}</p>
    </div>
  );
}

// Usage with destructuring
function UserCard({ name, email, age }) {
  return (
    <div className="user-card">
      <h2>{name}</h2>
      <p>{email}</p>
      <p>Age: {age}</p>
    </div>
  );
}

// Parent component
function App() {
  return <UserCard name="John" email="john@example.com" age={30} />;
}

Props Characteristics:

  • Immutable (read-only)
  • Flow downward (parent to child)
  • Can be any data type (strings, numbers, objects, functions, etc.)
  • Can have default values using defaultProps

4. State

State is mutable data managed within a component that can trigger re-renders when updated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

State Characteristics:

  • Local to the component
  • Asynchronous updates
  • Triggers re-render when changed
  • Should be treated as immutable

5. Virtual DOM

The Virtual DOM is a lightweight in-memory representation of the actual DOM. React uses it to optimize rendering performance.

How It Works:

  1. Initial Render: React creates a Virtual DOM tree
  2. State Change: When state updates, React creates a new Virtual DOM tree
  3. Diffing: React compares (diffs) the new tree with the previous one
  4. Reconciliation: React calculates the minimal set of changes needed
  5. Update: Only the necessary changes are applied to the real DOM

Benefits:

  • Minimizes expensive DOM operations
  • Batches multiple updates for efficiency
  • Enables declarative programming model

6. Reconciliation Algorithm

Reconciliation is the process React uses to determine what changes need to be made to the DOM.

Key Principles:

  • Element Type Comparison: If elements are of different types, React tears down the old tree and builds a new one
  • Key Prop: Use unique key props in lists to help React identify which items have changed
  • Component Updates: React only updates the changed components and their children
1
2
3
4
5
6
7
8
9
// Without keys (inefficient)
<ul>
  {items.map(item => <li>{item.name}</li>)}
</ul>

// With keys (efficient)
<ul>
  {items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>

Component Lifecycle

Understanding the component lifecycle is crucial for managing side effects, data fetching, and cleanup operations.

Lifecycle Overview

Every React component goes through three main phases:

  1. Mounting: Component is being created and inserted into the DOM
  2. Updating: Component is being re-rendered due to changes in props or state
  3. Unmounting: Component is being removed from the DOM

Class Component Lifecycle Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class MyComponent extends React.Component {
  // MOUNTING PHASE
  constructor(props) {
    super(props);
    this.state = { data: null };
    // Initialize state, bind methods
  }

  static getDerivedStateFromProps(props, state) {
    // Sync state with props (rarely used)
    return null;
  }

  componentDidMount() {
    // After first render
    // Ideal for: API calls, subscriptions, DOM manipulation
    fetch('/api/data')
      .then(res => res.json())
      .then(data => this.setState({ data }));
  }

  // UPDATING PHASE
  shouldComponentUpdate(nextProps, nextState) {
    // Return false to prevent re-render (optimization)
    return true;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Capture DOM info before update (rarely used)
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // After re-render
    // Ideal for: DOM updates, network requests based on prop changes
    if (prevProps.userId !== this.props.userId) {
      this.fetchUserData(this.props.userId);
    }
  }

  // UNMOUNTING PHASE
  componentWillUnmount() {
    // Before component removal
    // Ideal for: Cleanup (timers, subscriptions, cancel requests)
    clearInterval(this.timer);
  }

  // ERROR HANDLING
  static getDerivedStateFromError(error) {
    // Update state to show fallback UI
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // Log error information
    console.error('Error caught:', error, info);
  }

  render() {
    return <div>{this.state.data}</div>;
  }
}

Functional Component Lifecycle with Hooks

Hooks provide lifecycle functionality in functional components:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import { useState, useEffect } from 'react';

function MyComponent({ userId }) {
  const [data, setData] = useState(null);

  // ComponentDidMount + ComponentDidUpdate
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// Usage: Search with debounce
function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      // API call only after user stops typing for 500ms
      fetch(`/api/search?q=${debouncedSearchTerm}`)
        .then(res => res.json())
        .then(data => console.log(data));
    }
  }, [debouncedSearchTerm]);

  return (
    <input
      value={searchTerm}
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="Search..."
    />
  );
}

// Throttle: limit execution to once per interval
function useThrottle(callback, delay) {
  const lastRan = useRef(Date.now());

  return useCallback((...args) => {
    if (Date.now() - lastRan.current >= delay) {
      callback(...args);
      lastRan.current = Date.now();
    }
  }, [callback, delay]);
}

// Usage: Scroll event handler
function ScrollComponent() {
  const handleScroll = useThrottle(() => {
    console.log('Scroll position:', window.scrollY);
  }, 200);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);

  return <div style=>Scroll me</div>;
}

Hooks Deep Dive

Hooks are functions that let you use state and other React features in functional components.

Rules of Hooks

  1. Only call hooks at the top level - Don’t call inside loops, conditions, or nested functions
  2. Only call hooks from React functions - Call from functional components or custom hooks

useState

Manages local component state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const [state, setState] = useState(initialValue);

// Multiple state variables
function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);

  return (
    <form>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <input type="number" value={age} onChange={(e) => setAge(Number(e.target.value))} />
    </form>
  );
}

// Object state
function UserProfile() {
  const [user, setUser] = useState({ name: '', email: '', age: 0 });

  const updateName = (name) => {
    setUser(prevUser => ({ ...prevUser, name })); // Spread to preserve other properties
  };

  return <div>{user.name}</div>;
}

// Functional updates (when new state depends on previous state)
function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(prevCount => prevCount + 1); // Safer for async updates
  };

  return <button onClick={increment}>Count: {count}</button>;
}

// Lazy initialization (for expensive computations)
const [state, setState] = useState(() => {
  return expensiveCalculation();
});

useEffect

Handles side effects (data fetching, subscriptions, manual DOM manipulation).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
useEffect(() => {
  // Effect code
  return () => {
    // Cleanup code (optional)
  };
}, [dependencies]);

// Data fetching
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false; // Prevent setting state on unmounted component

    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) {
          setUser(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err);
          setLoading(false);
        }
      });

    return () => {
      cancelled = true; // Cleanup
    };
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{user.name}</div>;
}

// Subscriptions with cleanup
function ChatRoom({ roomId }) {
  useEffect(() => {
    const subscription = chatAPI.subscribe(roomId, message => {
      console.log('New message:', message);
    });

    return () => {
      subscription.unsubscribe(); // Cleanup
    };
  }, [roomId]);

  return <div>Chat Room {roomId}</div>;
}

// Multiple effects for separation of concerns
function Profile() {
  useEffect(() => {
    // Effect 1: Fetch user data
    fetchUserData();
  }, []);

  useEffect(() => {
    // Effect 2: Track analytics
    trackPageView();
  }, []);

  return <div>Profile</div>;
}

useContext

Accesses context values without prop drilling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { createContext, useContext, useState } from 'react';

// Create context
const ThemeContext = createContext();

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

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

  return (
    <ThemeContext.Provider value=Jekyll::Drops::ThemeDrop>
      {children}
    </ThemeContext.Provider>
  );
}

// Consumer component
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button
      style=
      onClick={toggleTheme}
    >
      Toggle Theme
    </button>
  );
}

// App structure
function App() {
  return (
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );
}

useReducer

Manages complex state logic with actions and reducers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { useReducer } from 'react';

// Reducer function
function reducer(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(reducer, { 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 example: Todo list
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.payload, completed: false }];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.payload);
    default:
      return state;
  }
}

function TodoList() {
  const [todos, dispatch] = useReducer(todoReducer, []);

  const addTodo = (text) => {
    dispatch({ type: 'ADD_TODO', payload: text });
  };

  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id}>
          <span style=>
            {todo.text}
          </span>
          <button onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}>
            Toggle
          </button>
          <button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
            Delete
          </button>
        </div>
      ))}
    </div>
  );
}

useRef

Creates a mutable reference that persists across renders without causing re-renders.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import { useRef, useEffect } from 'react';

// DOM manipulation
function TextInput() {
  const inputRef = useRef(null);

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

  useEffect(() => {
    inputRef.current.focus(); // Auto-focus on mount
  }, []);

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

// Storing previous values
function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  useEffect(() => {
    prevCountRef.current = count;
  });

  const prevCount = prevCountRef.current;

  return (
    <div>
      <p>Current: {count}, Previous: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

// Storing mutable values without re-rendering
function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef();

  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };

  useEffect(() => {
    return () => clearInterval(intervalRef.current); // Cleanup
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

useMemo

Memoizes expensive computations to avoid recalculating on every render.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { useMemo, useState } from 'react';

function ExpensiveComponent({ items, filter }) {
  // Expensive calculation only runs when items or filter changes
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    return items.filter(item => item.includes(filter));
  }, [items, filter]);

  return (
    <ul>
      {filteredItems.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

// Complex calculation example
function DataProcessor({ data, config }) {
  const processedData = useMemo(() => {
    return data
      .filter(item => item.value > config.threshold)
      .map(item => ({
        ...item,
        normalized: item.value / config.maxValue
      }))
      .sort((a, b) => b.normalized - a.normalized);
  }, [data, config]);

  return <div>{/* Render processedData */}</div>;
}

useCallback

Memoizes function references to prevent unnecessary re-renders of child components.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { useCallback, memo } from 'react';

// Child component (memoized to prevent unnecessary re-renders)
const Button = memo(({ onClick, children }) => {
  console.log(`Rendering button: ${children}`);
  return <button onClick={onClick}>{children}</button>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(0);

  // Without useCallback, this creates a new function on every render
  // const increment = () => setCount(c => c + 1);

  // With useCallback, function reference stays the same
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []); // Empty deps = function never changes

  return (
    <div>
      <p>Count: {count}</p>
      <Button onClick={increment}>Increment</Button>
      <button onClick={() => setOtherState(s => s + 1)}>
        Other State: {otherState}
      </button>
    </div>
  );
}

// Example with dependencies
function SearchComponent({ onSearch }) {
  const [query, setQuery] = useState('');
  const [filters, setFilters] = useState({});

  const handleSearch = useCallback(() => {
    onSearch(query, filters);
  }, [query, filters, onSearch]); // Recreate when dependencies change

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={handleSearch}>Search</button>
    </div>
  );
}

Custom Hooks

Reusable stateful logic extracted into functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Custom hook for form handling
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);

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

  const resetForm = () => {
    setValues(initialValues);
  };

  return { values, handleChange, resetForm };
}

// Usage
function LoginForm() {
  const { values, handleChange, resetForm } = useForm({
    username: '',
    password: ''
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(values);
    resetForm();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="username" value={values.username} onChange={handleChange} />
      <input name="password" type="password" value={values.password} onChange={handleChange} />
      <button type="submit">Login</button>
    </form>
  );
}

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

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

    fetch(url)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) {
          setData(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err);
          setLoading(false);
        }
      });

    return () => {
      cancelled = true;
    };
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{user.name}</div>;
}

// Custom hook for local storage
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

// Usage
function Settings() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

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

State Management

Local State vs Global State

Local State: Managed within a component using useState or useReducer

  • Use for: UI state, form inputs, toggles specific to one component

Global State: Shared across multiple components

  • Use for: User authentication, theme, language preferences, shopping cart

Context API for Global State

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import { createContext, useContext, useReducer } from 'react';

// Define initial state and reducer
const initialState = {
  user: null,
  theme: 'light',
  notifications: []
};

function appReducer(state, action) {
  switch (action.type) {
    case 'SET_USER':
      return { ...state, user: action.payload };
    case 'SET_THEME':
      return { ...state, theme: action.payload };
    case 'ADD_NOTIFICATION':
      return {
        ...state,
        notifications: [...state.notifications, action.payload]
      };
    case 'REMOVE_NOTIFICATION':
      return {
        ...state,
        notifications: state.notifications.filter(n => n.id !== action.payload)
      };
    default:
      return state;
  }
}

// Create context
const AppContext = createContext();

// Provider component
export function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);

  const value = {
    state,
    setUser: (user) => dispatch({ type: 'SET_USER', payload: user }),
    setTheme: (theme) => dispatch({ type: 'SET_THEME', payload: theme }),
    addNotification: (notification) => dispatch({ type: 'ADD_NOTIFICATION', payload: notification }),
    removeNotification: (id) => dispatch({ type: 'REMOVE_NOTIFICATION', payload: id })
  };

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}

// Custom hook for easy access
export function useApp() {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useApp must be used within AppProvider');
  }
  return context;
}

// Usage in components
function UserProfile() {
  const { state, setUser } = useApp();

  return (
    <div>
      {state.user ? (
        <p>Welcome, {state.user.name}</p>
      ) : (
        <button onClick={() => setUser({ name: 'John' })}>Login</button>
      )}
    </div>
  );
}

Prop Drilling Problem and Solutions

Prop Drilling: Passing props through multiple levels of components that don’t need them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Problem: Prop drilling
function App() {
  const [user, setUser] = useState(null);
  return <Parent user={user} setUser={setUser} />;
}

function Parent({ user, setUser }) {
  return <Child user={user} setUser={setUser} />; // Parent doesn't use these
}

function Child({ user, setUser }) {
  return <GrandChild user={user} setUser={setUser} />; // Child doesn't use these
}

function GrandChild({ user, setUser }) {
  return <div>{user?.name}</div>; // Only GrandChild uses them
}

// Solution 1: Context API
const UserContext = createContext();

function App() {
  const [user, setUser] = useState(null);
  return (
    <UserContext.Provider value=>
      <Parent />
    </UserContext.Provider>
  );
}

function Parent() {
  return <Child />; // No props needed
}

function Child() {
  return <GrandChild />; // No props needed
}

function GrandChild() {
  const { user } = useContext(UserContext);
  return <div>{user?.name}</div>;
}

// Solution 2: Component Composition
function App() {
  const [user, setUser] = useState(null);
  return (
    <Parent>
      <Child>
        <GrandChild user={user} />
      </Child>
    </Parent>
  );
}

Performance Optimization

1. React.memo

Prevents unnecessary re-renders of functional components by memoizing the result.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { memo } from 'react';

// Without memo: re-renders on every parent render
function ExpensiveComponent({ data }) {
  console.log('Rendering...');
  return <div>{data}</div>;
}

// With memo: only re-renders when props change
const MemoizedComponent = memo(function ExpensiveComponent({ data }) {
  console.log('Rendering...');
  return <div>{data}</div>;
});

// Custom comparison function
const MemoizedComponent = memo(
  function ExpensiveComponent({ data }) {
    return <div>{data.value}</div>;
  },
  (prevProps, nextProps) => {
    // Return true if props are equal (skip re-render)
    return prevProps.data.value === nextProps.data.value;
  }
);

2. Code Splitting and Lazy Loading

Load components only when needed to reduce initial bundle size.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { lazy, Suspense } from 'react';

// Lazy load component
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));

function App() {
  const [currentPage, setCurrentPage] = useState('dashboard');

  return (
    <div>
      <nav>
        <button onClick={() => setCurrentPage('dashboard')}>Dashboard</button>
        <button onClick={() => setCurrentPage('profile')}>Profile</button>
        <button onClick={() => setCurrentPage('settings')}>Settings</button>
      </nav>

      <Suspense fallback={<div>Loading...</div>}>
        {currentPage === 'dashboard' && <Dashboard />}
        {currentPage === 'profile' && <Profile />}
        {currentPage === 'settings' && <Settings />}
      </Suspense>
    </div>
  );
}

// Route-based code splitting
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading page...</div>}>
        <Routes>
          <Route path="/" element={<Dashboard />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

3. Virtualization for Large Lists

Render only visible items in large lists.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Using react-window library
import { FixedSizeList } from 'react-window';

function LargeList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      Item {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

4. Debouncing and Throttling

Control frequency of expensive operations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { useState, useEffect, useCallback } from 'react';

// Debounce: wait until user stops typing
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
      fetch(`/api/users/${userId}`)
        .then(res => res.json())
        .then(data => setData(data));
    }, [userId]); // Dependency array
  
    // ComponentDidMount only (empty dependency array)
    useEffect(() => {
      console.log('Component mounted');
    }, []);
  
    // ComponentDidUpdate only (runs after every render except first)
    useEffect(() => {
      console.log('Component updated');
    });
  
    // ComponentWillUnmount (cleanup function)
    useEffect(() => {
      const timer = setInterval(() => {
        console.log('Tick');
      }, 1000);
  
      return () => {
        clearInterval(timer); // Cleanup
      };
    }, []);
  
    return <div>{data?.name}</div>;
  }

5. Avoiding Inline Functions and Objects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Bad: Creates new function on every render
function Parent() {
  return <Child onClick={() => console.log('clicked')} />;
}

// Good: Stable function reference
function Parent() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return <Child onClick={handleClick} />;
}

// Bad: Creates new object on every render
function Parent() {
  return <Child style= />;
}

// Good: Stable object reference
const buttonStyle = { color: 'red' };

function Parent() {
  return <Child style={buttonStyle} />;
}

// Or use useMemo for dynamic styles
function Parent({ isActive }) {
  const buttonStyle = useMemo(() => ({
    color: isActive ? 'red' : 'blue',
    fontWeight: isActive ? 'bold' : 'normal'
  }), [isActive]);

  return <Child style={buttonStyle} />;
}

6. Key Prop Optimization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Bad: Using index as key (can cause issues with reordering)
{items.map((item, index) => (
  <Item key={index} data={item} />
))}

// Good: Using unique identifier
{items.map(item => (
  <Item key={item.id} data={item} />
))}

// For static lists where order never changes, index is acceptable
const STATIC_MENU = ['Home', 'About', 'Contact'];
{STATIC_MENU.map((item, index) => (
  <MenuItem key={index}>{item}</MenuItem>
))}

Best Practices

1. Component Design

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Single Responsibility Principle
// Bad: Component does too much
function UserDashboard() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [comments, setComments] = useState([]);
  
  // Lots of logic...
  
  return (
    <div>
      {/* Lots of JSX */}
    </div>
  );
}

// Good: Split into smaller components
function UserDashboard() {
  return (
    <div>
      <UserProfile />
      <UserPosts />
      <UserComments />
    </div>
  );
}

function UserProfile() {
  const [user, setUser] = useState(null);
  // Profile-specific logic
  return <div>{/* Profile JSX */}</div>;
}

function UserPosts() {
  const [posts, setPosts] = useState([]);
  // Posts-specific logic
  return <div>{/* Posts JSX */}</div>;
}

function UserComments() {
  const [comments, setComments] = useState([]);
  // Comments-specific logic
  return <div>{/* Comments JSX */}</div>;
}

2. State Management

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Keep state as close to where it's used as possible
// Bad: Lifting state unnecessarily high
function App() {
  const [modalOpen, setModalOpen] = useState(false);
  
  return (
    <div>
      <Header />
      <Content />
      <Footer modalOpen={modalOpen} setModalOpen={setModalOpen} />
    </div>
  );
}

// Good: State lives where it's needed
function Footer() {
  const [modalOpen, setModalOpen] = useState(false);
  
  return (
    <footer>
      <button onClick={() => setModalOpen(true)}>Open Modal</button>
      {modalOpen && <Modal onClose={() => setModalOpen(false)} />}
    </footer>
  );
}

// Derive state when possible instead of storing it
// Bad: Storing derived state
function ProductList({ products }) {
  const [filteredProducts, setFilteredProducts] = useState([]);
  const [filter, setFilter] = useState('all');

  useEffect(() => {
    setFilteredProducts(
      products.filter(p => filter === 'all' || p.category === filter)
    );
  }, [products, filter]);

  return <div>{/* Render filteredProducts */}</div>;
}

// Good: Calculate derived state
function ProductList({ products }) {
  const [filter, setFilter] = useState('all');

  const filteredProducts = products.filter(
    p => filter === 'all' || p.category === filter
  );

  return <div>{/* Render filteredProducts */}</div>;
}

3. Props and PropTypes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import PropTypes from 'prop-types';

// Define prop types for documentation and validation
function UserCard({ name, email, age, onEdit, isActive }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
      <p>Age: {age}</p>
      {isActive && <span>Active</span>}
      <button onClick={onEdit}>Edit</button>
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  email: PropTypes.string.isRequired,
  age: PropTypes.number,
  onEdit: PropTypes.func.isRequired,
  isActive: PropTypes.bool
};

UserCard.defaultProps = {
  age: 0,
  isActive: false
};

// Or use TypeScript for type safety
interface UserCardProps {
  name: string;
  email: string;
  age?: number;
  onEdit: () => void;
  isActive?: boolean;
}

function UserCard({ name, email, age = 0, onEdit, isActive = false }: UserCardProps) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
      <p>Age: {age}</p>
      {isActive && <span>Active</span>}
      <button onClick={onEdit}>Edit</button>
    </div>
  );
}

4. Error Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import { Component } from 'react';

// Error Boundary Component
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) {
    console.error('Error caught by boundary:', error, errorInfo);
    this.setState({ error, errorInfo });
    // Log to error reporting service
    // logErrorToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Something went wrong.</h1>
          <details style=>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

// Handling errors in async operations
function DataFetcher() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      const response = await fetch('/api/data');
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
      console.error('Fetch error:', err);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!data) return <button onClick={fetchData}>Load Data</button>;
  
  return <div>{JSON.stringify(data)}</div>;
}

5. Controlled vs Uncontrolled Components

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Controlled Component: React controls the form state
function ControlledForm() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: ''
  });

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

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form data:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={formData.username}
        onChange={handleChange}
        placeholder="Username"
      />
      <input
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="password"
        type="password"
        value={formData.password}
        onChange={handleChange}
        placeholder="Password"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

// Uncontrolled Component: DOM controls the form state
function UncontrolledForm() {
  const usernameRef = useRef();
  const emailRef = useRef();
  const passwordRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form data:', {
      username: usernameRef.current.value,
      email: emailRef.current.value,
      password: passwordRef.current.value
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={usernameRef} name="username" placeholder="Username" />
      <input ref={emailRef} name="email" placeholder="Email" />
      <input ref={passwordRef} name="password" type="password" placeholder="Password" />
      <button type="submit">Submit</button>
    </form>
  );
}

// When to use which:
// Controlled: When you need validation, conditional rendering, or dynamic forms
// Uncontrolled: For simple forms or when integrating with non-React code

6. Conditional Rendering Patterns

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 1. If-else with early return
function Greeting({ user }) {
  if (!user) {
    return <div>Please log in</div>;
  }

  return <div>Welcome, {user.name}</div>;
}

// 2. Ternary operator
function Status({ isOnline }) {
  return (
    <div>
      User is {isOnline ? 'online' : 'offline'}
    </div>
  );
}

// 3. Logical AND (&&)
function Notifications({ count }) {
  return (
    <div>
      {count > 0 && <span>You have {count} notifications</span>}
    </div>
  );
}

// 4. Switch statement for multiple conditions
function UserRole({ role }) {
  switch (role) {
    case 'admin':
      return <AdminDashboard />;
    case 'moderator':
      return <ModeratorDashboard />;
    case 'user':
      return <UserDashboard />;
    default:
      return <GuestView />;
  }
}

// 5. Object mapping for cleaner switch alternatives
function UserRole({ role }) {
  const roleComponents = {
    admin: <AdminDashboard />,
    moderator: <ModeratorDashboard />,
    user: <UserDashboard />,
    guest: <GuestView />
  };

  return roleComponents[role] || <GuestView />;
}

// 6. Null coalescing for default values
function Avatar({ user }) {
  return (
    <img
      src={user?.avatar ?? '/default-avatar.png'}
      alt={user?.name ?? 'Anonymous'}
    />
  );
}

7. Event Handling Best Practices

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 1. Prevent default behavior
function Form() {
  const handleSubmit = (e) => {
    e.preventDefault(); // Prevent page reload
    console.log('Form submitted');
  };

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

// 2. Stop event propagation
function NestedButtons() {
  const handleParentClick = () => console.log('Parent clicked');
  const handleChildClick = (e) => {
    e.stopPropagation(); // Prevent parent handler from firing
    console.log('Child clicked');
  };

  return (
    <div onClick={handleParentClick}>
      <button onClick={handleChildClick}>Click me</button>
    </div>
  );
}

// 3. Passing arguments to event handlers
function ItemList({ items, onDelete }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button onClick={() => onDelete(item.id)}>Delete</button>
          {/* Or use bind */}
          <button onClick={onDelete.bind(null, item.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

// 4. Synthetic events (React's cross-browser wrapper)
function InputHandler() {
  const handleChange = (e) => {
    // e is a SyntheticEvent
    console.log(e.target.value); // Safe to use
    
    // If you need the native event:
    console.log(e.nativeEvent);
  };

  return <input onChange={handleChange} />;
}

// 5. Event pooling (older React versions)
function OlderReactPattern() {
  const handleClick = (e) => {
    // In React 16 and below, need to persist for async access
    e.persist();
    
    setTimeout(() => {
      console.log(e.target.value); // Now safe
    }, 1000);
  };

  return <button onClick={handleClick}>Click</button>;
}

8. Folder Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
src/
β”œβ”€β”€ components/           # Reusable components
β”‚   β”œβ”€β”€ common/          # Shared components (Button, Input, Modal)
β”‚   β”‚   β”œβ”€β”€ Button.jsx
β”‚   β”‚   β”œβ”€β”€ Input.jsx
β”‚   β”‚   └── Modal.jsx
β”‚   β”œβ”€β”€ layout/          # Layout components (Header, Footer, Sidebar)
β”‚   β”‚   β”œβ”€β”€ Header.jsx
β”‚   β”‚   β”œβ”€β”€ Footer.jsx
β”‚   β”‚   └── Sidebar.jsx
β”‚   └── features/        # Feature-specific components
β”‚       β”œβ”€β”€ UserProfile/
β”‚       β”‚   β”œβ”€β”€ UserProfile.jsx
β”‚       β”‚   β”œβ”€β”€ UserProfile.module.css
β”‚       β”‚   └── index.js
β”‚       └── ProductList/
β”‚           β”œβ”€β”€ ProductList.jsx
β”‚           β”œβ”€β”€ ProductItem.jsx
β”‚           └── index.js
β”œβ”€β”€ hooks/               # Custom hooks
β”‚   β”œβ”€β”€ useAuth.js
β”‚   β”œβ”€β”€ useFetch.js
β”‚   └── useLocalStorage.js
β”œβ”€β”€ context/             # Context providers
β”‚   β”œβ”€β”€ AuthContext.jsx
β”‚   β”œβ”€β”€ ThemeContext.jsx
β”‚   └── index.js
β”œβ”€β”€ pages/               # Page components (for routing)
β”‚   β”œβ”€β”€ Home.jsx
β”‚   β”œβ”€β”€ About.jsx
β”‚   β”œβ”€β”€ Dashboard.jsx
β”‚   └── NotFound.jsx
β”œβ”€β”€ services/            # API calls and external services
β”‚   β”œβ”€β”€ api.js
β”‚   β”œβ”€β”€ auth.js
β”‚   └── storage.js
β”œβ”€β”€ utils/               # Utility functions
β”‚   β”œβ”€β”€ formatDate.js
β”‚   β”œβ”€β”€ validation.js
β”‚   └── constants.js
β”œβ”€β”€ styles/              # Global styles
β”‚   β”œβ”€β”€ globals.css
β”‚   β”œβ”€β”€ variables.css
β”‚   └── reset.css
β”œβ”€β”€ App.jsx              # Main App component
└── main.jsx             # Entry point

9. Naming Conventions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Components: PascalCase
function UserProfile() {}
const ProductCard = () => {};

// Files: Match component name
// UserProfile.jsx
// ProductCard.tsx

// Props: camelCase
<UserCard userName="John" isActive={true} onUpdate={handleUpdate} />

// Event handlers: handle + EventName
const handleClick = () => {};
const handleSubmit = () => {};
const handleChange = () => {};

// Boolean props/state: is/has/should prefix
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
const [shouldRender, setShouldRender] = useState(true);

// Custom hooks: use + Functionality
function useAuth() {}
function useFetch(url) {}
function useLocalStorage(key) {}

// Constants: UPPER_SNAKE_CASE
const API_BASE_URL = 'https://api.example.com';
const MAX_RETRIES = 3;

// Functions: camelCase, descriptive verbs
function fetchUserData() {}
function calculateTotal() {}
function validateEmail() {}

10. Testing Best Practices

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Using React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

describe('Counter Component', () => {
  test('renders initial count', () => {
    render(<Counter />);
    expect(screen.getByText('Count: 0')).toBeInTheDocument();
  });

  test('increments count when button clicked', () => {
    render(<Counter />);
    const button = screen.getByRole('button', { name: /increment/i });
    fireEvent.click(button);
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });

  test('decrements count when button clicked', async () => {
    render(<Counter initialCount={5} />);
    const button = screen.getByRole('button', { name: /decrement/i });
    await userEvent.click(button);
    expect(screen.getByText('Count: 4')).toBeInTheDocument();
  });
});

// Testing async operations
test('loads and displays data', async () => {
  render(<DataFetcher url="/api/data" />);
  
  // Initially shows loading
  expect(screen.getByText('Loading...')).toBeInTheDocument();
  
  // Wait for data to load
  await waitFor(() => {
    expect(screen.getByText('Data loaded')).toBeInTheDocument();
  });
});

// Testing with Context
import { ThemeProvider } from './ThemeContext';

test('applies theme from context', () => {
  render(
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );
  
  const button = screen.getByRole('button');
  expect(button).toHaveStyle({ background: '#fff' });
});

Lifecycle Terminology Tables

Table 1: Lifecycle Phase Terminology Comparison

PhaseClass Component TermFunctional Component TermWhat HappensCommon Use Cases
CreationMountingInitial RenderComponent is created and inserted into DOMSetting up initial state, refs
InitializationConstructoruseState initializationState and props are initializedDefining initial values
First PaintcomponentDidMountuseEffect with [] depsComponent rendered to DOM for first timeAPI calls, subscriptions, DOM manipulation
Re-renderUpdatingRe-renderComponent updates due to state/props changeReflecting new data in UI
Update CheckshouldComponentUpdateReact.memo comparisonDetermine if re-render is neededPerformance optimization
Post-UpdatecomponentDidUpdateuseEffect with dependenciesAfter component updates in DOMResponding to prop/state changes, side effects
DestructionUnmountingCleanup function in useEffectComponent is removed from DOMCleanup: clear timers, cancel requests, unsubscribe
Error StatecomponentDidCatchError Boundary onlyComponent catches errors in child componentsError logging, fallback UI

Table 2: Hierarchical Lifecycle Method Terminology

CategorySubcategoryClass Component MethodFunctional Component EquivalentExecution OrderDescription
MOUNTINGInitializationconstructor()Component function execution + useState()1Initialize state and bind methods
MOUNTINGStatic Propsstatic getDerivedStateFromProps()Calculate during render2Sync state with props (rarely used)
MOUNTINGRenderingrender()Component function return3Return JSX to be rendered
MOUNTINGCommitcomponentDidMount()useEffect(() => {}, [])4After first render, DOM available
UPDATINGProps Changestatic getDerivedStateFromProps()Calculate during render1Sync state with new props
UPDATINGShould UpdateshouldComponentUpdate()React.memo()2Optimization: prevent unnecessary renders
UPDATINGRe-renderingrender()Component function return3Generate new Virtual DOM
UPDATINGPre-commitgetSnapshotBeforeUpdate()No direct equivalent4Capture DOM info before update
UPDATINGCommitcomponentDidUpdate()useEffect(() => {}, [deps])5After re-render, DOM updated
UNMOUNTINGCleanupcomponentWillUnmount()useEffect(() => { return () => {} }, [])1Before component removal
ERRORError Statestatic getDerivedStateFromError()No functional equivalent1Update state to show fallback UI
ERRORError LoggingcomponentDidCatch()No functional equivalent2Log error details

Table 3: Hook Execution Timing

HookExecution PhaseTimingTriggersUse For
useStateInitialization + UpdatesDuring renderState changesManaging component state
useEffect (no deps)After every renderAfter paintEvery renderSide effects on every update
useEffect with []After mount onlyAfter first paintOnce on mountInitialization, data fetching
useEffect with depsAfter mount + specific updatesAfter paint when deps changeDependency changesResponding to specific changes
useEffect cleanupBefore unmount + before next effectBefore effect re-run or unmountDependency changes or unmountCleanup: timers, subscriptions
useLayoutEffectAfter render, before paintSynchronously after DOM updatesSimilar to useEffectDOM measurements, synchronous updates
useMemoDuring renderDuring render, when deps changeDependency changesExpensive calculations
useCallbackDuring renderDuring render, when deps changeDependency changesMemoizing functions
useRefInitializationOnce on mountNever triggers re-renderPersisting values, DOM refs
useReducerInitialization + DispatchDuring render + when dispatchedAction dispatchComplex state logic
useContextDuring renderDuring renderContext value changesAccessing context

Table 4: State Update Terminology

PatternClass ComponentFunctional ComponentBehaviorWhen to Use
Direct Updatethis.setState({ count: 5 })setCount(5)Replaces state with new valueWhen new state is independent of old state
Functional Updatethis.setState(prev => ({ count: prev.count + 1 }))setCount(prev => prev + 1)Updates based on previous stateWhen new state depends on old state
Batched UpdatesMultiple setState calls batchedMultiple setState calls batchedReact batches updates for performanceAlways (automatic in React 18+)
Async UpdatesState updates are asynchronousState updates are asynchronousState doesn’t update immediatelyAlways (fundamental React behavior)
Force Updatethis.forceUpdate()Not available (not needed)Forces re-render without state changeAvoid; use proper state management

Table 5: Component Communication Patterns

PatternDirectionMechanismExampleUse Case
PropsParent β†’ ChildProps<Child data={value} />Passing data down
Callback PropsChild β†’ ParentFunction props<Child onChange={handleChange} />Child notifying parent
ContextAny β†’ Any (within provider)Context APIuseContext(MyContext)Global state, avoiding prop drilling
RefsParent β†’ ChildRef forwarding<Child ref={childRef} />Accessing child methods/DOM
State LiftingSiblings via ParentLift state to common ancestorState in parent, props to childrenSharing state between siblings
Custom EventsAny β†’ AnyEvent system (non-React)window.dispatchEvent()Cross-component communication
Global StateAny β†’ AnyExternal libraryRedux, Zustand, JotaiComplex app-wide state

References


Last Updated: December 2024

Author’s Note: This guide covers React fundamentals through advanced concepts. For hands-on practice, build projects progressively: start with a simple counter, then a todo list, then a full application with routing and state management. The best way to master React is through consistent practice and experimentation.

This post is licensed under CC BY 4.0 by the author.