React Interview Questions

Fundamentals

What is React?

React is a JavaScript library developed by Facebook for building user interfaces, particularly for web applications. It follows a component-based architecture where UIs are broken down into reusable, independent pieces that manage their own state.

Key concepts:

  • Component-Based Architecture: React applications are built using components, which are self-contained units of UI that can be composed together to create complex interfaces.
  • Virtual DOM: React maintains a lightweight copy of the main browser DOM in memory. When state changes occur, React creates a new Virtual DOM tree, compares it with the previous one, and then updates only the parts of the actual DOM that have changed.
  • Unidirectional Data Flow: Data flows in a single direction, from parent components down to child components through props.
  • JSX: A syntax extension that allows you to write HTML-like code directly in your JavaScript.
const element = <h1>Hello, world!</h1>;

const element = React.createElement('h1', null, 'Hello, world!');

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

const element = <Welcome name="Sara" />;
Fundamentals

What is JSX?

JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like code to define the structure of your UI components. It makes React code more readable and expressive.

Important points:

  • HTML attributes are written in camelCase, such as className instead of class.
  • Self-closing tags must have a trailing slash: <img />.
  • JavaScript expressions can be embedded within JSX using curly braces .
  • JSX expressions must have one parent element or be wrapped in a React Fragment.
const user = { name: 'Alice', age: 30 };
const isLoggedIn = true;

const greeting = <div>Hello, {user.name}! You are {user.age} years old.</div>;

const message = (
  <div>
    {isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>}
  </div>
);

const list = (
  <React.Fragment>
    <h1>Shopping List</h1>
    <ul>
      <li>Bread</li>
      <li>Milk</li>
      <li>Eggs</li>
    </ul>
  </React.Fragment>
);

const shortFragment = (
  <>
    <h1>Shopping List</h1>
    <ul>
      <li>Bread</li>
      <li>Milk</li>
      <li>Eggs</li>
    </ul>
  </>
);

const buttonProps = {
  type: 'button',
  className: 'btn btn-primary',
  onClick: () => console.log('Button clicked')
};

const button = <button {...buttonProps}>Click me</button>;
Fundamentals

What is the Virtual DOM and reconciliation?

The Virtual DOM is an in-memory representation of the real DOM. When a component's state changes, React uses a process called reconciliation to update the UI efficiently:

  1. A new Virtual DOM tree is generated to match the updated state.
  2. This new tree is compared with the previous one in a step known as "diffing" to identify what has changed.
  3. React applies only the necessary changes to the real DOM.

How the diffing algorithm works:

  • When comparing elements of different types, React tears down the old tree and builds a new one.
  • When comparing elements of the same type, React keeps the same DOM node and only updates changed attributes.
  • Keys help React identify which elements have changed, added, or removed.
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
Fundamentals

Props vs State

Props and state are both JavaScript objects that hold information influencing component output, but they serve different purposes:

PropsState
Read-only (immutable)Mutable (can be changed)
Passed from parent to childManaged internally by the component
Triggers re-render when changed by parentTriggers re-render when updated
Used for component configurationUsed for data that changes over time
function Parent() {
  const [count, setCount] = useState(0);
  
  return <Child count={count} onIncrement={() => setCount(c => c + 1)} />;
}

function Child({ count, onIncrement }) {
  return (
    <div>
      <p>Current count: {count}</p>
      <button onClick={onIncrement}>Increment</button>
    </div>
  );
}

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  increment = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  }
  
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}
Fundamentals

Functional vs Class Components

Functional Components (modern standard):

  • Simple JavaScript functions that accept props and return JSX
  • Use Hooks for state and side effects
  • Easier to test and read
  • Less boilerplate code

Class Components (legacy):

  • ES6 classes that extend React.Component
  • Use this.state and lifecycle methods
  • More verbose with constructor, binding, etc.
const Greet = ({ name }) => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]);
  
  return (
    <div>
      <h1>Hello {name}!</h1>
      <button onClick={() => setCount(count + 1)}>
        Clicked {count} times
      </button>
    </div>
  );
};

class Greet extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }
  
  handleClick() {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  }
  
  render() {
    return (
      <div>
        <h1>Hello {this.props.name}!</h1>
        <button onClick={this.handleClick}>
          Clicked {this.state.count} times
        </button>
      </div>
    );
  }
}
Fundamentals

React Component Lifecycle

Component lifecycle methods are special methods that automatically get called as components mount, update, and unmount.

Class Component Lifecycle Methods:

  • Mounting:
    • constructor() - Initialize state and bind methods
    • static getDerivedStateFromProps() - Update state based on props
    • render() - Return JSX
    • componentDidMount() - Run side effects after mounting
  • Updating:
    • static getDerivedStateFromProps() - Update state based on props
    • shouldComponentUpdate() - Control re-rendering
    • render() - Return JSX
    • getSnapshotBeforeUpdate() - Capture DOM info before changes
    • componentDidUpdate() - Run side effects after updating
  • Unmounting:
    • componentWillUnmount() - Clean up timers, subscriptions
class LifecycleExample extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    console.log('Constructor: Component is being created');
  }
  
  static getDerivedStateFromProps(props, state) {
    console.log('getDerivedStateFromProps: Updating state based on props');
    return null;
  }
  
  componentDidMount() {
    console.log('componentDidMount: Component has mounted to the DOM');
  }
  
  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate: Should component re-render?');
    return true;
  }
  
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('getSnapshotBeforeUpdate: Capturing DOM info before update');
    return null;
  }
  
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('componentDidUpdate: Component has updated');
  }
  
  componentWillUnmount() {
    console.log('componentWillUnmount: Component is about to be removed');
  }
  
  render() {
    console.log('render: Component is rendering');
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

function FunctionalLifecycle() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log('useEffect: Component has mounted or updated');
    
    return () => {
      console.log('useEffect cleanup: Component is about to unmount');
    };
  }, [count]);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
Hooks

What are Hooks?

Hooks are functions that allow you to use state and other React features in functional components. They were introduced in React 16.8.

Benefits of Hooks:

  • Allow functional components to have state and lifecycle methods
  • Enable better code organization through custom hooks
  • Eliminate the need for class components and this
  • Make it easier to share stateful logic between components

Rules of Hooks:

  • Hooks must be called at the top level of a function
  • Hooks can only be called from React functional components or custom hooks
function CounterWithEffects() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  useEffect(() => {
    document.title = `Count: ${count}`;
    
    return () => {
      console.log('Component will unmount or count will change');
    };
  }, [count]);
  
  const theme = useContext(ThemeContext);
  const inputRef = useRef(null);
  
  const expensiveValue = useMemo(() => {
    return computeExpensiveValue(count);
  }, [count]);
  
  const handleClick = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);
  
  return (
    <div className={theme}>
      <input ref={inputRef} value={name} onChange={e => setName(e.target.value)} />
      <p>Count: {count}</p>
      <p>Expensive value: {expensiveValue}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}
Hooks

Explain useState with example

useState is a Hook that lets you add state to function components. It returns an array with the current state value and a function to update it.

Syntax:

const [state, setState] = useState(initialState);

Key points:

  • The initial state is only used during the first render
  • Calling the state update function triggers a re-render
  • State updates are asynchronous and may be batched
  • You can update state using either a new value or a function
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>
  );
}

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

  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

  const incrementFiveTimes = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={incrementFiveTimes}>Increment 5 Times</button>
    </div>
  );
}

function UserProfile() {
  const [user, setUser] = useState({
    name: 'John',
    age: 30,
    email: 'john@example.com'
  });

  const updateName = (newName) => {
    setUser(prevUser => ({
      ...prevUser,
      name: newName
    }));
  };

  const updateAge = (newAge) => {
    setUser(prevUser => ({
      ...prevUser,
      age: newAge
    }));
  };

  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
      <p>Email: {user.email}</p>
      <input 
        type="text" 
        value={user.name} 
        onChange={e => updateName(e.target.value)} 
      />
      <input 
        type="number" 
        value={user.age} 
        onChange={e => updateAge(parseInt(e.target.value))} 
      />
    </div>
  );
}
Hooks

Explain useEffect with an example

useEffect is a Hook that lets you perform side effects in function components. It serves the same purpose as lifecycle methods in class components.

Syntax:

useEffect(() => {
    return () => {
    };
  }, [dependency1, dependency2]);

Dependency array controls behavior:

  • [] → runs once on mount and cleanup on unmount
  • [dep1, dep2] → runs when any dependency changes
  • No array → runs after every render
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        const response = await fetch(`https://api.example.com/users/${userId}`);
        const data = await response.json();
        setUser(data);
        setError(null);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, [userId]);

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

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

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();

    const handleNewMessage = (message) => {
      setMessages(prevMessages => [...prevMessages, message]);
    };
    connection.on('message', handleNewMessage);

    return () => {
      connection.disconnect();
      connection.off('message', handleNewMessage);
    };
  }, [roomId]);

  return (
    <div>
      <h1>Room: {roomId}</h1>
      <ul>
        {messages.map((message, index) => (
          <li key={index}>{message.text}</li>
        ))}
      </ul>
    </div>
  );
}

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [isActive, setIsActive] = useState(false);

  useEffect(() => {
    let interval = null;

    if (isActive) {
      interval = setInterval(() => {
        setSeconds(seconds => seconds + 1);
      }, 1000);
    } else if (!isActive && seconds !== 0) {
      clearInterval(interval);
    }

    return () => clearInterval(interval);
  }, [isActive, seconds]);

  const toggle = () => setIsActive(!isActive);
  const reset = () => {
    setSeconds(0);
    setIsActive(false);
  };

  return (
    <div>
      <div>{seconds}s</div>
      <button onClick={toggle}>
        {isActive ? 'Pause' : 'Start'}
      </button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
Hooks

How does useContext work?

useContext is a Hook that lets you subscribe to React context without introducing nesting.

When to use Context:

  • Sharing global data like theme, user authentication, or language preferences
  • Avoiding prop drilling through multiple component levels
  • Sharing state between components that are not directly related
const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <Toolbar />
      <button onClick={toggleTheme}>Toggle Theme</button>
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  const style = {
    backgroundColor: theme === 'dark' ? '#333' : '#FFF',
    color: theme === 'dark' ? '#FFF' : '#333',
    padding: '10px',
    border: '1px solid',
    borderColor: theme === 'dark' ? '#FFF' : '#333'
  };
  
  return (
    <button style={style} onClick={toggleTheme}>
      I am a {theme} button
    </button>
  );
}

const UserContext = createContext(null);
const ThemeContext = createContext('light');

function App() {
  const [user, setUser] = useState({ name: 'John', id: 1 });
  const [theme, setTheme] = useState('light');
  
  return (
    <UserContext.Provider value={user}>
      <ThemeContext.Provider value={theme}>
        <Header />
        <Main />
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

function Header() {
  const user = useContext(UserContext);
  const theme = useContext(ThemeContext);
  
  return (
    <header className={theme}>
      Welcome, {user.name}!
    </header>
  );
}
Hooks

When to use useReducer?

useReducer is an alternative to useState for managing complex state logic or when the next state depends on the previous one.

When to use useReducer instead of useState:

  • When you have complex state with multiple sub-values
  • When the next state depends on the previous state
  • When you want to optimize performance for components that trigger deep updates
  • When you want to make state updates more predictable
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 };
    case 'reset':
      return initialState;
    default:
      throw new Error();
  }
}

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

const initialState = {
  todos: [],
  filter: 'all',
  nextId: 1
};

function todoReducer(state, action) {
  switch (action.type) {
    case 'add':
      return {
        ...state,
        todos: [
          ...state.todos,
          { id: state.nextId, text: action.text, completed: false }
        ],
        nextId: state.nextId + 1
      };
    case 'toggle':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.id
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    case 'delete':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.id)
      };
    case 'setFilter':
      return {
        ...state,
        filter: action.filter
      };
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  const [input, setInput] = useState('');
  
  const addTodo = () => {
    if (input.trim()) {
      dispatch({ type: 'add', text: input });
      setInput('');
    }
  };
  
  const filteredTodos = state.todos.filter(todo => {
    if (state.filter === 'completed') return todo.completed;
    if (state.filter === 'active') return !todo.completed;
    return true;
  });
  
  return (
    <div>
      <input 
        value={input} 
        onChange={e => setInput(e.target.value)} 
        onKeyPress={e => e.key === 'Enter' && addTodo()}
      />
      <button onClick={addTodo}>Add</button>
      
      <div>
        <button onClick={() => dispatch({ type: 'setFilter', filter: 'all' })}>
          All
        </button>
        <button onClick={() => dispatch({ type: 'setFilter', filter: 'active' })}>
          Active
        </button>
        <button onClick={() => dispatch({ type: 'setFilter', filter: 'completed' })}>
          Completed
        </button>
      </div>
      
      <ul>
        {filteredTodos.map(todo => (
          <li 
            key={todo.id} 
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
            onClick={() => dispatch({ type: 'toggle', id: todo.id })}
          >
            {todo.text}
            <button onClick={() => dispatch({ type: 'delete', id: todo.id })}>
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}
Hooks

useMemo vs useCallback

useMemo and useCallback are hooks used for performance optimization by preventing unnecessary re-renders and re-computations.

useMemo

Memoizes the result of a function call and only re-computes it when dependencies change.

Syntax:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useCallback

Memoizes a callback function itself, preventing it from being re-created on every render.

Syntax:

const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);
function ExpensiveComponent({ a, b }) {
  const expensiveValue = useMemo(() => {
    console.log('Running expensive calculation...');
    return computeExpensiveValue(a, b);
  }, [a, b]);
  
  return <div>Result: {expensiveValue}</div>;
}

const Button = React.memo(({ onClick, children }) => {
  console.log('Button rendered');
  return <button onClick={onClick}>{children}</button>;
});

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

function TodoList({ todos, onToggle, onDelete }) {
  const filteredTodos = useMemo(() => {
    return todos.filter(todo => !todo.completed);
  }, [todos]);
  
  const handleToggle = useCallback((id) => {
    onToggle(id);
  }, [onToggle]);
  
  const handleDelete = useCallback((id) => {
    onDelete(id);
  }, [onDelete]);
  
  return (
    <ul>
      {filteredTodos.map(todo => (
        <TodoItem 
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </ul>
  );
}

const TodoItem = React.memo(({ todo, onToggle, onDelete }) => {
  return (
    <li>
      <span 
        style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
        onClick={() => onToggle(todo.id)}
      >
        {todo.text}
      </span>
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
});
Hooks

Explain useRef Hook

useRef is a Hook that returns a mutable ref object whose .current property persists for the full lifetime of the component.

Common use cases:

  • Accessing DOM elements directly
  • Storing values that persist across re-renders without causing re-renders
  • Storing previous values for comparison
  • Storing mutable values that don't affect the render output
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);
  
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
    
    return () => {
      clearInterval(intervalRef.current);
    };
  }, []);
  
  const handleStop = () => {
    clearInterval(intervalRef.current);
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleStop}>Stop Timer</button>
    </div>
  );
}

function PreviousValueExample() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();
  
  useEffect(() => {
    prevCountRef.current = count;
  });
  
  const prevCount = prevCountRef.current;
  
  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  
  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function MeasureElement() {
  const [height, setHeight] = useState(0);
  const elementRef = useRef(null);
  
  useEffect(() => {
    if (elementRef.current) {
      setHeight(elementRef.current.getBoundingClientRect().height);
    }
  }, []);
  
  return (
    <div>
      <div ref={elementRef} style={{ padding: '20px', background: 'lightblue' }}>
        This is the element being measured
      </div>
      <p>Height: {height}px</p>
    </div>
  );
}
Hooks

How to create custom Hooks?

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

Benefits of custom Hooks:

  • Reuse stateful logic between components
  • Separate concerns and make components more focused
  • Share logic without changing component hierarchy
  • Test complex logic in isolation
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);
        setError(null);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

function UserProfile({ userId }) {
  const { data: user, loading, error } = useApi(`https://api.example.com/users/${userId}`);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return <div>{user.name}</div>;
}

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

function App() {
  const [name, setName] = useLocalStorage('name', 'John');
  
  return (
    <input 
      type="text" 
      value={name} 
      onChange={e => setName(e.target.value)} 
    />
  );
}

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

function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
  
  useEffect(() => {
    if (debouncedSearchTerm) {
      console.log('Searching for:', debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);
  
  return (
    <input 
      type="text" 
      value={searchTerm} 
      onChange={e => setSearchTerm(e.target.value)} 
      placeholder="Search..."
    />
  );
}

function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });
  
  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };
    
    window.addEventListener('resize', handleResize);
    
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return windowSize;
}

function ResponsiveComponent() {
  const { width, height } = useWindowSize();
  
  return (
    <div>
      Window size: {width} x {height}
      {width < 768 && <p>This is a mobile view</p>}
      {width >= 768 && <p>This is a desktop view</p>}
    </div>
  );
}
Performance & Optimization

Performance optimizations in React

React applications can suffer from performance issues due to unnecessary re-renders, expensive calculations, and large bundle sizes. Here are several techniques to optimize React applications:

  • Assign stable and unique keys to list items to help React identify which elements have changed.
  • Use React.memo to prevent components from re-rendering if their props have not changed.
  • Use useMemo and useCallback to avoid re-running expensive calculations or re-creating functions.
  • Implement code splitting with React.lazy and Suspense to enable on-demand loading.
  • Use virtualization for long lists to render only visible items.
  • Avoid inline functions and objects in render as they create new instances on every render.
  • Optimize state updates by batching multiple state updates or using functional updates.
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

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

const ExpensiveComponent = React.memo(({ data, onClick }) => {
  console.log('ExpensiveComponent rendered');
  return (
    <div>
      {data.map(item => (
        <div key={item.id} onClick={() => onClick(item.id)}>
          {item.name}
        </div>
      ))}
    </div>
  );
});

function DataProcessor({ items }) {
  const processedData = useMemo(() => {
    console.log('Processing data...');
    return items.map(item => {
      return {
        ...item,
        processed: expensiveComputation(item)
      };
    });
  }, [items]);
  
  return (
    <div>
      {processedData.map(item => (
        <div key={item.id}>{item.processed}</div>
      ))}
    </div>
  );
}

function ParentComponent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback((id) => {
    console.log('Item clicked:', id);
  }, []);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <MemoizedChild onClick={handleClick} />
    </div>
  );
}

const MemoizedChild = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={() => onClick(1)}>Click me</button>;
});

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

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );
  
  return (
    <List
      height={500}
      itemCount={items.length}
      itemSize={35}
      width={300}
    >
      {Row}
    </List>
  );
}

function OptimizedCounter() {
  const [state, setState] = useState({ count: 0, name: 'John' });
  
  const badIncrement = () => {
    setState({ ...state, count: state.count + 1 });
  };
  
  const goodIncrement = () => {
    setState(prevState => ({ ...prevState, count: prevState.count + 1 }));
  };
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={goodIncrement}>Increment</button>
    </div>
  );
}
Performance & Optimization

What is React.memo?

React.memo is a higher-order component that memoizes a functional component. It prevents re-renders if the component's props have not changed since the last render.

How it works:

  • React.memo performs a shallow comparison of the component's props.
  • If the props haven't changed, React skips rendering the component.
  • If the props have changed, React renders the component and updates the DOM.

When to use React.memo:

  • Components that render often with the same props
  • Components that are expensive to render
  • Components that are part of a frequently re-rendering parent
const MemoizedComponent = React.memo(function MyComponent(props) {
  return <div>{props.value}</div>;
});

const User = React.memo(({ name, age }) => {
  console.log('User component rendered');
  return (
    <div>
      <p>Name: {name}</p>
      <p>Age: {age}</p>
    </div>
  );
});

function App() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ name: 'John', age: 30 });
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
      <User name={user.name} age={user.age} />
    </div>
  );
}

const User = React.memo(
  ({ name, age }) => {
    console.log('User component rendered');
    return (
      <div>
        <p>Name: {name}</p>
        <p>Age: {age}</p>
      </div>
    );
  },
  (prevProps, nextProps) => {
    return (
      prevProps.name === nextProps.name && 
      prevProps.age === nextProps.age
    );
  }
);

const UserProfile = React.memo(({ user, onUpdate }) => {
  console.log('UserProfile rendered');
  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <button onClick={() => onUpdate(user.id)}>Update</button>
    </div>
  );
});

function App() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ id: 1, name: 'John', email: 'john@example.com' });
  
  const handleUpdate = useCallback((userId) => {
    console.log('Updating user:', userId);
  }, []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
      <UserProfile user={user} onUpdate={handleUpdate} />
    </div>
  );
}

function BadExample() {
  const [count, setCount] = useState(0);
  
  const data = { value: count };
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <MemoizedChild data={data} />
    </div>
  );
}

const MemoizedChild = React.memo(({ data }) => {
  console.log('Child rendered');
  return <div>{data.value}</div>;
});

function GoodExample() {
  const [count, setCount] = useState(0);
  
  const data = useMemo(() => ({ value: count }), [count]);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <MemoizedChild data={data} />
    </div>
  );
}
Performance & Optimization

Code Splitting in React

Code splitting is a technique to split your code into smaller chunks that can be loaded on demand. React provides built-in support for code splitting through React.lazy and Suspense.

Benefits of code splitting:

  • Reduced initial bundle size
  • Faster initial page load
  • Better user experience on slower networks
  • Improved caching efficiency
import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

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

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const Dashboard = lazy(() => import('./routes/Dashboard'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <nav>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/dashboard">Dashboard</Link></li>
        </ul>
      </nav>
      
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  </Router>
);

const ComponentA = React.lazy(() => 
  import('./components').then(module => ({
    default: module.ComponentA
  }))
);

import { ErrorBoundary } from 'react-error-boundary';

const MyComponent = () => (
  <ErrorBoundary
    fallback={
      <div>
        <h2>Something went wrong!</h2>
        <button onClick={() => window.location.reload()}>
          Try again
        </button>
      </div>
    }
  >
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  </ErrorBoundary>
);

function DynamicImport({ load, fallback }) {
  const [Component, setComponent] = useState(null);
  
  useEffect(() => {
    load().then(module => {
      setComponent(() => module.default);
    });
  }, [load]);
  
  if (!Component) return fallback;
  return <Component />;
}

function App() {
  return (
    <DynamicImport 
      load={() => import('./HeavyComponent')}
      fallback={<div>Loading...</div>}
    />
  );
}
Advanced Topics

When to use Context API?

The Context API enables you to share values between different components without explicitly passing props through every level of the component tree.

When to use Context API:

  • Sharing theme data (light/dark mode) across the application
  • Managing user authentication status
  • Sharing language or locale preferences
  • Providing application configuration
  • Sharing data needed by many components at different nesting levels

When NOT to use Context API:

  • For state that updates frequently (can cause performance issues)
  • For sharing data between just a few components
  • For complex state management (consider Redux, MobX, or Zustand)
const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {},
});

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  const value = {
    theme,
    toggleTheme,
  };
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
};

const Header = () => {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <header className={theme}>
      <h1>My App</h1>
      <button onClick={toggleTheme}>
        Switch to {theme === 'light' ? 'dark' : 'light'} mode
      </button>
    </header>
  );
};

const UserContext = createContext(null);
const ThemeContext = createContext('light');

function App() {
  const [user, setUser] = useState({ name: 'John', id: 1 });
  const [theme, setTheme] = useState('light');
  
  return (
    <UserContext.Provider value={user}>
      <ThemeContext.Provider value={theme}>
        <Header />
        <Main />
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

function Header() {
  const user = useContext(UserContext);
  const theme = useContext(ThemeContext);
  
  return (
    <header className={theme}>
      Welcome, {user.name}!
    </header>
  );
}

const initialState = {
  isAuthenticated: false,
  user: null,
  token: null,
};

function authReducer(state, action) {
  switch (action.type) {
    case 'LOGIN':
      return {
        ...state,
        isAuthenticated: true,
        user: action.payload.user,
        token: action.payload.token,
      };
    case 'LOGOUT':
      return {
        ...state,
        isAuthenticated: false,
        user: null,
        token: null,
      };
    default:
      return state;
  }
}

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, initialState);
  
  const login = (user, token) => {
    dispatch({ type: 'LOGIN', payload: { user, token } });
  };
  
  const logout = () => {
    dispatch({ type: 'LOGOUT' });
  };
  
  const value = {
    ...state,
    login,
    logout,
  };
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

const CountContext = createContext();

function CountProvider({ children }) {
  const [count, setCount] = useState(0);
  
  const value = useMemo(() => ({ count, setCount }), [count]);
  
  return (
    <CountContext.Provider value={value}>
      {children}
    </CountContext.Provider>
  );
}

const CountStateContext = createContext();
const CountDispatchContext = createContext();

function CountProvider({ children }) {
  const [count, setCount] = useState(0);
  
  return (
    <CountStateContext.Provider value={count}>
      <CountDispatchContext.Provider value={setCount}>
        {children}
      </CountDispatchContext.Provider>
    </CountStateContext.Provider>
  );
}

function useCountState() {
  return useContext(CountStateContext);
}

function useCountDispatch() {
  return useContext(CountDispatchContext);
}

function CountDisplay() {
  const count = useCountState();
  return <div>Count: {count}</div>;
}

function CountButton() {
  const setCount = useCountDispatch();
  return <button onClick={() => setCount(c => c + 1)}>Increment</button>;
}
Advanced Topics

What does 'lifting state up' mean?

"Lifting state up" is a pattern where you move a piece of state from a child component to its nearest common ancestor. This allows different components to share and modify the same state.

Why lift state up:

  • To share state between multiple components
  • To ensure that components are in sync with each other
  • To follow the single source of truth principle
  • To make state changes more predictable
function CelsiusInput() {
  const [temperature, setTemperature] = useState('');
  
  const handleChange = (e) => {
    setTemperature(e.target.value);
  };
  
  return (
    <fieldset>
      <legend>Enter temperature in Celsius:</legend>
      <input
        value={temperature}
        onChange={handleChange}
      />
    </fieldset>
  );
}

function FahrenheitInput() {
  const [temperature, setTemperature] = useState('');
  
  const handleChange = (e) => {
    setTemperature(e.target.value);
  };
  
  return (
    <fieldset>
      <legend>Enter temperature in Fahrenheit:</legend>
      <input
        value={temperature}
        onChange={handleChange}
      />
    </fieldset>
  );
}

function Calculator() {
  return (
    <div>
      <CelsiusInput />
      <FahrenheitInput />
    </div>
  );
}

function Calculator() {
  const [temperature, setTemperature] = useState('');
  const [scale, setScale] = useState('c');
  
  const handleCelsiusChange = (temperature) => {
    setTemperature(temperature);
    setScale('c');
  };
  
  const handleFahrenheitChange = (temperature) => {
    setTemperature(temperature);
    setScale('f');
  };
  
  const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
  const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
  
  return (
    <div>
      <TemperatureInput
        scale="c"
        temperature={celsius}
        onTemperatureChange={handleCelsiusChange}
      />
      <TemperatureInput
        scale="f"
        temperature={fahrenheit}
        onTemperatureChange={handleFahrenheitChange}
      />
      <BoilingVerdict
        celsius={parseFloat(celsius)}
      />
    </div>
  );
}

function TemperatureInput({ scale, temperature, onTemperatureChange }) {
  const scaleNames = {
    c: 'Celsius',
    f: 'Fahrenheit'
  };
  
  return (
    <fieldset>
      <legend>Enter temperature in {scaleNames[scale]}:</legend>
      <input
        value={temperature}
        onChange={(e) => onTemperatureChange(e.target.value)}
      />
    </fieldset>
  );
}

function Form() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: ''
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prevData => ({
      ...prevData,
      [name]: value
    }));
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <UsernameInput
        value={formData.username}
        onChange={handleChange}
      />
      <EmailInput
        value={formData.email}
        onChange={handleChange}
      />
      <PasswordInput
        value={formData.password}
        onChange={handleChange}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

function UsernameInput({ value, onChange }) {
  return (
    <div>
      <label>Username:</label>
      <input
        type="text"
        name="username"
        value={value}
        onChange={onChange}
      />
    </div>
  );
}

function Accordion() {
  const [activeIndex, setActiveIndex] = useState(null);
  
  const handleItemClick = (index) => {
    setActiveIndex(activeIndex === index ? null : index);
  };
  
  return (
    <div>
      {items.map((item, index) => (
        <AccordionItem
          key={index}
          title={item.title}
          content={item.content}
          isActive={activeIndex === index}
          onClick={() => handleItemClick(index)}
        />
      ))}
    </div>
  );
}

function AccordionItem({ title, content, isActive, onClick }) {
  return (
    <div>
      <div onClick={onClick}>{title}</div>
      {isActive && <div>{content}</div>}
    </div>
  );
}
Advanced Topics

What are Error Boundaries?

Error Boundaries are React components that catch JavaScript errors in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.

Key points about Error Boundaries:

  • They catch errors during rendering, in lifecycle methods, and in constructors.
  • They do not catch errors in event handlers, asynchronous code, or server-side rendering.
  • They are class components that implement either static getDerivedStateFromError() or componentDidCatch().
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

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

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    logErrorToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

function App() {
  return (
    <div>
      <Navbar />
      <ErrorBoundary>
        <MainContent />
      </ErrorBoundary>
      <Footer />
    </div>
  );
}

class ErrorBoundary extends React.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);
    logErrorToService(error, errorInfo);
    
    this.setState({
      error: error,
      errorInfo: errorInfo
    });
  }
  
  handleReset = () => {
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
          <button onClick={this.handleReset}>Try again</button>
        </div>
      );
    }

    return this.props.children;
  }
}

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onError={(error, errorInfo) => logErrorToService(error, errorInfo)}
      onReset={() => {
      }}
    >
      <MyComponent />
    </ErrorBoundary>
  );
}

function asyncOperation() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Async operation failed'));
    }, 1000);
  });
}

class AsyncComponent extends React.Component {
  state = { error: null };
  
  componentDidMount() {
    asyncOperation()
      .catch(error => this.setState({ error }));
  }
  
  render() {
    if (this.state.error) {
      throw this.state.error;
    }
    
    return <div>Async operation completed successfully</div>;
  }
}

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Router>
      <Navbar />
      <ErrorBoundary>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </ErrorBoundary>
      <Footer />
    </Router>
  );
}
Advanced Topics

What is Concurrent React (React 18+)?

Concurrent React is a feature that enables React to work on multiple state updates simultaneously. It can interrupt, pause, or resume rendering to prioritize important updates, like user input, for a smoother user experience.

  • Transitions allow you to mark certain state updates as non-urgent, so they do not block more critical updates.
  • Suspense offers a declarative way to manage loading states while waiting for data to be fetched.
  • Automatic batching groups multiple state updates into a single re-render, which improves application performance.
function OldComponent() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  function handleClick() {
    setCount(c => c + 1);
    setFlag(f => !f);
  }
  
  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? 'blue' : 'black' }}>{count}</h1>
    </div>
  );
}

function NewComponent() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  function handleClick() {
    setCount(c => c + 1);
    setFlag(f => !f);
  }
  
  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? 'blue' : 'black' }}>{count}</h1>
    </div>
  );
}

import { startTransition } from 'react';

function SearchPage() {
  const [inputValue, setInputValue] = useState('');
  const [searchQuery, setSearchQuery] = useState('');
  const [results, setResults] = useState([]);
  
  const handleChange = (e) => {
    setInputValue(e.target.value);
    
    startTransition(() => {
      setSearchQuery(e.target.value);
    });
  };
  
  useEffect(() => {
    if (searchQuery) {
      fetchResults(searchQuery).then(setResults);
    }
  }, [searchQuery]);
  
  return (
    <div>
      <input 
        type="text" 
        value={inputValue} 
        onChange={handleChange} 
        placeholder="Search..."
      />
      <SearchResults results={results} />
    </div>
  );
}

import { useTransition } from 'react';

function SearchPage() {
  const [isPending, startTransition] = useTransition();
  const [inputValue, setInputValue] = useState('');
  const [searchQuery, setSearchQuery] = useState('');
  const [results, setResults] = useState([]);
  
  const handleChange = (e) => {
    setInputValue(e.target.value);
    
    startTransition(() => {
      setSearchQuery(e.target.value);
    });
  };
  
  useEffect(() => {
    if (searchQuery) {
      fetchResults(searchQuery).then(setResults);
    }
  }, [searchQuery]);
  
  return (
    <div>
      <input 
        type="text" 
        value={inputValue} 
        onChange={handleChange} 
        placeholder="Search..."
      />
      {isPending && <p>Searching...</p>}
      <SearchResults results={results} />
    </div>
  );
}

import { Suspense } from 'react';

function UserProfile({ userId }) {
  const user = fetchUser(userId);
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<div>Loading profile...</div>}>
        <UserProfile userId={1} />
      </Suspense>
      <Suspense fallback={<div>Loading posts...</div>}>
        <UserPosts userId={1} />
      </Suspense>
    </div>
  );
}

import { useDeferredValue } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  
  const results = useMemo(() => {
    if (!deferredQuery) return [];
    return searchProducts(deferredQuery);
  }, [deferredQuery]);
  
  return (
    <ul>
      {results.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}

function SearchPage() {
  const [query, setQuery] = useState('');
  
  return (
    <div>
      <input 
        type="text" 
        value={query} 
        onChange={e => setQuery(e.target.value)} 
        placeholder="Search products..."
      />
      <SearchResults query={query} />
    </div>
  );
}
Advanced Topics

SSR vs CSR in React

Server-Side Rendering (SSR) and Client-Side Rendering (CSR) are two approaches to rendering React applications, each with its own advantages and trade-offs.

Client-Side Rendering (CSR):

  • The browser downloads a minimal HTML file and a JavaScript bundle.
  • React runs in the browser, generates the HTML, and renders the UI.
  • This can result in a slower initial load time and may not be ideal for SEO.
  • Navigating to other pages is often faster since it does not require a full page reload.

Server-Side Rendering (SSR):

  • The server generates the full HTML for the page and sends it to the browser.
  • The user can see the content immediately, even before JavaScript loads.
  • This approach leads to a faster initial page load and is more SEO-friendly.
  • After the initial render, the client-side JavaScript takes over in a process called "hydration."
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './Home';
import About from './About';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Router>
  );
}

export default App;

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

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

function HomePage() {
  return (
    <div>
      <h1>Welcome to My Website</h1>
      <p>This is rendered on the server.</p>
    </div>
  );
}

export default HomePage;

import '../styles/globals.css';

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default MyApp;

function Post({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
}

export async function getServerSideProps(context) {
  const { id } = context.params;
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  const post = await res.json();
  
  return {
    props: {
      post,
    },
  };
}

export default Post;

function BlogPost({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </div>
  );
}

export async function getStaticPaths() {
  const posts = await getAllPosts();
  const paths = posts.map(post => ({
    params: { slug: post.slug }
  }));
  
  return {
    paths,
    fallback: false,
  };
}

export async function getStaticProps({ params }) {
  const post = await getPostBySlug(params.slug);
  
  return {
    props: {
      post,
    },
  };
}

export default BlogPost;

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();
  
  return {
    props: {
      data,
    },
    revalidate: 60,
  };
}

import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './src/App';

const app = express();

app.get('*', (req, res) => {
  const appString = ReactDOMServer.renderToString(<App />);
  
  const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>SSR React App</title>
      </head>
      <body>
        <div id="root">${appString}</div>
        <script src="client.bundle.js"></script>
      </body>
    </html>
  `;
  
  res.send(html);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

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

Higher-Order Components (HOCs)

A Higher-Order Component (HOC) is an advanced React pattern for reusing component logic. It's a function that takes a component and returns a new component with additional props or behavior.

Common use cases for HOCs:

  • Code reuse, logic abstraction, and state manipulation
  • Props manipulation and abstraction
  • Rendering hijacking and state abstraction
  • Adding lifecycle methods to functional components
function withLogging(WrappedComponent) {
  return function WithLoggingComponent(props) {
    console.log('Props:', props);
    return <WrappedComponent {...props} />;
  };
}

const MyComponent = (props) => <div>Hello {props.name}</div>;
const EnhancedComponent = withLogging(MyComponent);

function withDataFetching(url) {
  return function(WrappedComponent) {
    return class WithDataFetching extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          data: null,
          loading: true,
          error: null
        };
      }
      
      componentDidMount() {
        fetch(url)
          .then(response => response.json())
          .then(data => this.setState({ data, loading: false }))
          .catch(error => this.setState({ error, loading: false }));
      }
      
      render() {
        const { data, loading, error } = this.state;
        
        if (loading) return <div>Loading...</div>;
        if (error) return <div>Error: {error.message}</div>;
        
        return <WrappedComponent data={data} {...this.props} />;
      }
    };
  };
}

const UserList = ({ data }) => (
  <ul>
    {data.map(user => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);

const UserListWithData = withDataFetching('https://api.example.com/users')(UserList);

function withAuth(WrappedComponent) {
  return function WithAuthComponent(props) {
    const { isAuthenticated } = useAuth();
    
    if (!isAuthenticated) {
      return <div>Please log in to view this content.</div>;
    }
    
    return <WrappedComponent {...props} />;
  };
}

const Dashboard = () => <div>Dashboard content</div>;
const ProtectedDashboard = withAuth(Dashboard);

function withStyles(styles) {
  return function(WrappedComponent) {
    return function WithStylesComponent(props) {
      const styleSheet = typeof styles === 'function' ? styles(props) : styles;
      
      return (
        <div style={styleSheet.container}>
          <WrappedComponent {...props} />
        </div>
      );
    };
  };
}

const Button = ({ children }) => <button>{children}</button>;
const StyledButton = withStyles({
  container: {
    backgroundColor: 'blue',
    color: 'white',
    padding: '10px'
  }
})(Button);

function compose(...hocs) {
  return function(Component) {
    return hocs.reduceRight((acc, hoc) => hoc(acc), Component);
  };
}

const EnhancedComponent = compose(
  withAuth,
  withStyles(buttonStyles),
  withLogging
)(MyComponent);

function withCounter(WrappedComponent) {
  return function WithCounterComponent(props) {
    const [count, setCount] = useState(0);
    
    const increment = () => setCount(count + 1);
    const decrement = () => setCount(count - 1);
    
    return (
      <WrappedComponent
        count={count}
        increment={increment}
        decrement={decrement}
        {...props}
      />
    );
  };
}

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

const EnhancedCounter = withCounter(CounterDisplay);
Advanced Topics

Render Props Pattern

The render props pattern is a technique in React for sharing code between components using a prop whose value is a function. A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.

Benefits of render props:

  • Allows you to share stateful logic between components
  • Provides more flexibility than HOCs
  • Makes the data flow more explicit
  • Avoids wrapper hell that can occur with multiple HOCs
class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

function App() {
  return (
    <div>
      <h1>Move the mouse around!</h1>
      <MouseTracker
        render={({ x, y }) => (
          <h1>The mouse position is ({x}, {y})</h1>
        )}
      />
    </div>
  );
}

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {this.props.children(this.state)}
      </div>
    );
  }
}

function App() {
  return (
    <div>
      <h1>Move the mouse around!</h1>
      <MouseTracker>
        {({ x, y }) => (
          <h1>The mouse position is ({x}, {y})</h1>
        )}
      </MouseTracker>
    </div>
  );
}

class DataFetcher extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: null,
      loading: true,
      error: null
    };
  }

  componentDidMount() {
    fetch(this.props.url)
      .then(response => response.json())
      .then(data => this.setState({ data, loading: false }))
      .catch(error => this.setState({ error, loading: false }));
  }

  render() {
    return this.props.children(this.state);
  }
}

function App() {
  return (
    <DataFetcher url="https://api.example.com/users">
      {({ data, loading, error }) => {
        if (loading) return <div>Loading...</div>;
        if (error) return <div>Error: {error.message}</div>;
        
        return (
          <ul>
            {data.map(user => (
              <li key={user.id}>{user.name}</li>
            ))}
          </ul>
        );
      }}
    </DataFetcher>
  );
}

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      values: this.props.initialValues || {},
      errors: {},
      touched: {},
      isSubmitting: false
    };
  }

  handleChange = (event) => {
    const { name, value } = event.target;
    this.setState({
      values: {
        ...this.state.values,
        [name]: value
      }
    });
  }

  handleBlur = (event) => {
    const { name } = event.target;
    this.setState({
      touched: {
        ...this.state.touched,
        [name]: true
      }
    });
  }

  handleSubmit = (event) => {
    event.preventDefault();
    this.setState({ isSubmitting: true });
    
    this.props.onSubmit(this.state.values)
      .then(() => this.setState({ isSubmitting: false }))
      .catch(error => this.setState({ isSubmitting: false, error }));
  }

  render() {
    return this.props.children({
      ...this.state,
      handleChange: this.handleChange,
      handleBlur: this.handleBlur,
      handleSubmit: this.handleSubmit
    });
  }
}

function LoginForm() {
  return (
    <Form
      initialValues={{ email: '', password: '' }}
      onSubmit={values => console.log('Form submitted:', values)}
    >
      {({
        values,
        errors,
        touched,
        isSubmitting,
        handleChange,
        handleBlur,
        handleSubmit
      }) => (
        <form onSubmit={handleSubmit}>
          <div>
            <label>Email</label>
            <input
              type="email"
              name="email"
              value={values.email}
              onChange={handleChange}
              onBlur={handleBlur}
            />
            {touched.email && errors.email && (
              <div>{errors.email}</div>
            )}
          </div>
          <div>
            <label>Password</label>
            <input
              type="password"
              name="password"
              value={values.password}
              onChange={handleChange}
              onBlur={handleBlur}
            />
            {touched.password && errors.password && (
              <div>{errors.password}</div>
            )}
          </div>
          <button type="submit" disabled={isSubmitting}>
            {isSubmitting ? 'Submitting...' : 'Submit'}
          </button>
        </form>
      )}
    </Form>
  );
}

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

function useMousePosition() {
  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 position;
}

function MousePosition() {
  const { x, y } = useMousePosition();
  
  return (
    <h1>The mouse position is ({x}, {y})</h1>
  );
}
Best Practices & Anti-Patterns

Common React Anti-Patterns

Anti-patterns in React are common coding practices that may seem to work but lead to problems in the long run. Here are some common React anti-patterns and how to avoid them:

  • Inline functions in render: Creates new function on every render → breaks React.memo.
  • Missing useEffect dependencies: Causes stale closures and bugs.
  • Using index as key: Breaks reconciliation when list order changes.
  • Overusing Context: Leads to unnecessary re-renders.
  • Mutating state directly: Bypasses React's state management.
  • Using setState in render: Causes infinite loops.
function TodoList({ items, onItemClick }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li 
          key={index}
          onClick={() => onItemClick(item.id)}
        >
          {item.text}
        </li>
      ))}
    </ul>
  );
}

function TodoList({ items, onItemClick }) {
  return (
    <ul>
      {items.map(item => (
        <TodoItem 
          key={item.id}
          item={item}
          onItemClick={onItemClick}
        />
      ))}
    </ul>
  );
}

const TodoItem = React.memo(({ item, onItemClick }) => {
  const handleClick = () => onItemClick(item.id);
  
  return (
    <li onClick={handleClick}>
      {item.text}
    </li>
  );
});

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []);
  
  return user ? <div>{user.name}</div> : <div>Loading...</div>;
}

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  return user ? <div>{user.name}</div> : <div>Loading...</div>;
}

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', completed: false }
  ]);
  
  const toggleTodo = (id) => {
    const todo = todos.find(t => t.id === id);
    todo.completed = !todo.completed;
    setTodos([...todos]);
  };
  
  return (
    <ul>
      {todos.map(todo => (
        <li 
          key={todo.id}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          onClick={() => toggleTodo(todo.id)}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', completed: false }
  ]);
  
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };
  
  return (
    <ul>
      {todos.map(todo => (
        <li 
          key={todo.id}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          onClick={() => toggleTodo(todo.id)}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

function Counter() {
  const [count, setCount] = useState(0);
  
  if (count < 10) {
    setCount(count + 1);
  }
  
  return <div>Count: {count}</div>;
}

function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    if (count < 10) {
      setCount(count + 1);
    }
  }, [count]);
  
  return <div>Count: {count}</div>;
}

function UserProfile({ user }) {
  const [fullName, setFullName] = useState(`${user.firstName} ${user.lastName}`);
  
  useEffect(() => {
    setFullName(`${user.firstName} ${user.lastName}`);
  }, [user.firstName, user.lastName]);
  
  return <div>{fullName}</div>;
}

function UserProfile({ user }) {
  const fullName = `${user.firstName} ${user.lastName}`;
  
  return <div>{fullName}</div>;
}

function App() {
  const [state, setState] = useState({
    user: null,
    theme: 'light',
    notifications: [],
    cart: [],
    searchQuery: ''
  });
  
  return (
    <AppContext.Provider value={{ state, setState }}>
      <Header />
      <Main />
      <Footer />
    </AppContext.Provider>
  );
}

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [notifications, setNotifications] = useState([]);
  const [cart, setCart] = useState([]);
  const [searchQuery, setSearchQuery] = useState('');
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <ThemeContext.Provider value={{ theme, setTheme }}>
        <NotificationsContext.Provider value={{ notifications, setNotifications }}>
          <CartContext.Provider value={{ cart, setCart }}>
            <Header searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
            <Main />
            <Footer />
          </CartContext.Provider>
        </NotificationsContext.Provider>
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}
Best Practices & Anti-Patterns

Controlled vs Uncontrolled Components

In React, form elements can be either controlled or uncontrolled. Understanding the difference between these two approaches is crucial for building forms and handling user input effectively.

Controlled Components:

  • Form elements whose values are controlled by React state
  • React state is the single source of truth for the input value
  • Changes to the input are handled through event handlers
  • More predictable and easier to validate

Uncontrolled Components:

  • Form elements that maintain their own internal state
  • React doesn't control the input's value
  • Values are accessed using refs
  • Simpler to implement for basic forms
function ControlledForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  
  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`Name: ${name}, Email: ${email}`);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

function UncontrolledForm() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);
  
  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`Name: ${nameRef.current.value}, Email: ${emailRef.current.value}`);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          type="text"
          ref={nameRef}
          defaultValue="John"
        />
      </div>
      <div>
        <label>Email:</label>
        <input
          type="email"
          ref={emailRef}
          defaultValue="john@example.com"
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

function ValidatedForm() {
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');
  
  const validateEmail = (email) => {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return re.test(email);
  };
  
  const handleChange = (e) => {
    const value = e.target.value;
    setEmail(value);
    
    if (value && !validateEmail(value)) {
      setError('Please enter a valid email address');
    } else {
      setError('');
    }
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (!validateEmail(email)) {
      setError('Please enter a valid email address');
      return;
    }
    
    alert(`Email submitted: ${email}`);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={email}
          onChange={handleChange}
          className={error ? 'error' : ''}
        />
        {error && <div className="error-message">{error}</div>}
      </div>
      <button type="submit" disabled={!email || !!error}>
        Submit
      </button>
    </form>
  );
}

function FileInput() {
  const fileInputRef = useRef(null);
  const [file, setFile] = useState(null);
  
  const handleChange = (e) => {
    setFile(e.target.files[0]);
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (file) {
      alert(`File selected: ${file.name}`);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>File:</label>
        <input
          type="file"
          ref={fileInputRef}
          onChange={handleChange}
        />
      </div>
      {file && <p>Selected file: {file.name}</p>}
      <button type="submit" disabled={!file}>
        Upload
      </button>
    </form>
  );
}

function FormWithDefaults() {
  const [name, setName] = useState('John Doe');
  const [email, setEmail] = useState('john@example.com');
  const [subscribe, setSubscribe] = useState(true);
  
  const resetForm = () => {
    setName('John Doe');
    setEmail('john@example.com');
    setSubscribe(true);
  };
  
  return (
    <form>
      <div>
        <label>Name:</label>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      <div>
        <label>
          <input
            type="checkbox"
            checked={subscribe}
            onChange={(e) => setSubscribe(e.target.checked)}
          />
          Subscribe to newsletter
        </label>
      </div>
      <button type="button" onClick={resetForm}>
        Reset
      </button>
    </form>
  );
}

function GetValueFromUncontrolled() {
  const inputRef = useRef(null);
  const [value, setValue] = useState('');
  
  const getValue = () => {
    setValue(inputRef.current.value);
  };
  
  return (
    <div>
      <input type="text" ref={inputRef} defaultValue="Type something" />
      <button onClick={getValue}>Get Value</button>
      <p>Current value: {value}</p>
    </div>
  );
}
Best Practices & Anti-Patterns

Testing React Components

Testing is an essential part of building reliable React applications. There are several approaches and tools available for testing React components.

Popular testing libraries for React:

  • React Testing Library: A lightweight solution for testing React components
  • Jest: A JavaScript testing framework
  • Enzyme: A JavaScript testing utility for React
  • Cypress: An end-to-end testing framework
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Counter from './Counter';

describe('Counter component', () => {
  test('renders initial count', () => {
    render(<Counter initialCount={0} />);
    expect(screen.getByText('Count: 0')).toBeInTheDocument();
  });
  
  test('increments count when button is clicked', () => {
    render(<Counter initialCount={0} />);
    
    const button = screen.getByText('Increment');
    fireEvent.click(button);
    
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });
  
  test('decrements count when button is clicked', () => {
    render(<Counter initialCount={5} />);
    
    const button = screen.getByText('Decrement');
    fireEvent.click(button);
    
    expect(screen.getByText('Count: 4')).toBeInTheDocument();
  });
});

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

global.fetch = jest.fn();

describe('UserList component', () => {
  beforeEach(() => {
    fetch.mockClear();
  });
  
  test('displays loading state initially', () => {
    fetch.mockReturnValue(Promise.resolve({
      json: () => Promise.resolve([])
    }));
    
    render(<UserList />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });
  
  test('displays user data after loading', async () => {
    const users = [
      { id: 1, name: 'John Doe' },
      { id: 2, name: 'Jane Smith' }
    ];
    
    fetch.mockReturnValue(Promise.resolve({
      json: () => Promise.resolve(users)
    }));
    
    render(<UserList />);
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
    });
  });
  
  test('displays error message when fetch fails', async () => {
    fetch.mockReturnValue(Promise.reject(new Error('Failed to fetch')));
    
    render(<UserList />);
    
    await waitFor(() => {
      expect(screen.getByText('Error: Failed to fetch')).toBeInTheDocument();
    });
  });
});

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

describe('useCounter hook', () => {
  test('should initialize with default value', () => {
    const { result } = renderHook(() => useCounter());
    
    expect(result.current.count).toBe(0);
  });
  
  test('should initialize with custom value', () => {
    const { result } = renderHook(() => useCounter(5));
    
    expect(result.current.count).toBe(5);
  });
  
  test('should increment count', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
  
  test('should decrement count', () => {
    const { result } = renderHook(() => useCounter(5));
    
    act(() => {
      result.current.decrement();
    });
    
    expect(result.current.count).toBe(4);
  });
});

import { shallow, mount } from 'enzyme';
import Counter from './Counter';

describe('Counter component with Enzyme', () => {
  test('renders initial count', () => {
    const wrapper = shallow(<Counter initialCount={0} />);
    expect(wrapper.find('p').text()).toContain('Count: 0');
  });
  
  test('increments count when button is clicked', () => {
    const wrapper = mount(<Counter initialCount={0} />);
    
    wrapper.find('button').at(0).simulate('click');
    
    expect(wrapper.find('p').text()).toContain('Count: 1');
  });
  
  test('calls onCountChange when count changes', () => {
    const mockFn = jest.fn();
    const wrapper = mount(<Counter initialCount={0} onCountChange={mockFn} />);
    
    wrapper.find('button').at(0).simulate('click');
    
    expect(mockFn).toHaveBeenCalledWith(1);
  });
});

import TestRenderer from 'react-test-renderer';
import Counter from './Counter';

describe('Counter component with React Test Renderer', () => {
  test('matches snapshot', () => {
    const tree = TestRenderer.create(<Counter initialCount={0} />).toJSON();
    expect(tree).toMatchSnapshot();
  });
  
  test('increments count', () => {
    const testRenderer = TestRenderer.create(<Counter initialCount={0} />);
    const instance = testRenderer.getInstance();
    
    instance.increment();
    
    expect(testRenderer.toJSON().children[0].children[1]).toBe('Count: 1');
  });
});
Best Practices & Anti-Patterns

State Management Patterns in React

As React applications grow in complexity, managing state becomes increasingly important. There are several patterns and libraries available for state management in React.

Common state management patterns:

  • Local Component State: Using useState for component-specific state
  • Lifting State Up: Moving state to the nearest common ancestor
  • Context API: For sharing state across the component tree
  • useReducer: For complex state logic
  • Redux: A predictable state container for JavaScript apps
  • MobX: Simple, scalable state management
  • Zustand: A small, fast, and scalable state management solution
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <Child count={count} onIncrement={() => setCount(count + 1)} />
    </div>
  );
}

function Child({ count, onIncrement }) {
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={onIncrement}>Increment</button>
    </div>
  );
}

const CountContext = createContext();

function CountProvider({ children }) {
  const [count, setCount] = useState(0);
  
  return (
    <CountContext.Provider value={{ count, setCount }}>
      {children}
    </CountContext.Provider>
  );
}

function useCount() {
  const context = useContext(CountContext);
  if (!context) {
    throw new Error('useCount must be used within a CountProvider');
  }
  return context;
}

function Counter() {
  const { count, setCount } = useCount();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

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:
      return state;
  }
}

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

import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });

function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    default:
      return state;
  }
}

const store = createStore(counterReducer);

function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>
  );
}

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

import create from 'zustand';

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

function Counter() {
  const { count, increment, decrement } = useCounterStore();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

const countState = atom({
  key: 'countState',
  default: 0,
});

const doubledCountState = selector({
  key: 'doubledCountState',
  get: ({ get }) => {
    const count = get(countState);
    return count * 2;
  },
});

function Counter() {
  const [count, setCount] = useRecoilState(countState);
  const doubledCount = useRecoilValue(doubledCountState);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubledCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
Best Practices & Anti-Patterns

React Router for Navigation

React Router is the standard routing library for React. It enables navigation among different components in a React application, allows changing the browser URL, and keeps the UI in sync with the URL.

Key concepts in React Router:

  • BrowserRouter: Uses the HTML5 history API to keep UI in sync with URL
  • Routes and Route: Define the mapping between URL paths and components
  • Link and NavLink: Create navigation links
  • useNavigate: Programmatically navigate to different routes
  • useParams: Access URL parameters
  • useLocation: Access the current location object
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Contact from './Contact';

function App() {
  return (
    <Router>
      <nav>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/contact">Contact</Link></li>
        </ul>
      </nav>
      
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </Router>
  );
}

import { useParams } from 'react-router-dom';

function User() {
  const { userId } = useParams();
  
  return (
    <div>
      <h2>User Profile</h2>
      <p>User ID: {userId}</p>
    </div>
  );
}

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/users/:userId" element={<User />} />
      </Routes>
    </Router>
  );
}

import { Outlet } from 'react-router-dom';

function Products() {
  return (
    <div>
      <h2>Products</h2>
      <nav>
        <Link to="featured">Featured</Link>
        <Link to="new">New</Link>
      </nav>
      
      <Outlet />
    </div>
  );
}

function FeaturedProducts() {
  return <h3>Featured Products</h3>;
}

function NewProducts() {
  return <h3>New Products</h3>;
}

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/products" element={<Products />}>
          <Route path="featured" element={<FeaturedProducts />} />
          <Route path="new" element={<NewProducts />} />
        </Route>
      </Routes>
    </Router>
  );
}

import { useNavigate } from 'react-router-dom';

function Login() {
  const navigate = useNavigate();
  
  const handleLogin = () => {
    navigate('/dashboard');
  };
  
  return (
    <div>
      <h2>Login</h2>
      <button onClick={handleLogin}>Log In</button>
    </div>
  );
}

import { useLocation } from 'react-router-dom';
import { useSearchParams } from 'react-router-dom';

function SearchResults() {
  const [searchParams] = useSearchParams();
  const query = searchParams.get('query');
  const page = searchParams.get('page') || 1;
  
  return (
    <div>
      <h2>Search Results</h2>
      <p>Query: {query}</p>
      <p>Page: {page}</p>
    </div>
  );
}

import { Navigate } from 'react-router-dom';

function ProtectedRoute({ children }) {
  const { isAuthenticated } = useAuth();
  
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  
  return children;
}

function Dashboard() {
  return (
    <div>
      <h2>Dashboard</h2>
      <p>Welcome to your dashboard!</p>
    </div>
  );
}

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

import { NavLink } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      <ul>
        <li>
          <NavLink 
            to="/" 
            className={({ isActive }) => isActive ? 'active' : ''}
          >
            Home
          </NavLink>
        </li>
        <li>
          <NavLink 
            to="/about" 
            className={({ isActive }) => isActive ? 'active' : ''}
          >
            About
          </NavLink>
        </li>
      </ul>
    </nav>
  );
}

import { lazy, Suspense } from 'react';

const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const Contact = lazy(() => import('./Contact'));

function App() {
  return (
    <Router>
      <nav>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/contact">Contact</Link></li>
        </ul>
      </nav>
      
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </Router>
  );
}