React
1. React Basics & Philosophy
Imagine you are building a custom workstation. You could buy a pre-configured office desk with drawers, cable holes, and stands built-in (that is Angular). Or, you could buy raw IKEA table tops, legs, brackets, and drawers (that is React). With React, you build exactly what you want, but you are responsible for choosing and assembling all the pieces (routing, state management, validation).
React is a declarative, component-based JavaScript library for building user interfaces, developed and maintained by Meta (Facebook) and a community of developers. Unlike Angular, React is opinionatedly thin — it focuses strictly on the View layer of the application. For routing, state management, and HTTP requests, developers rely on the rich React ecosystem (e.g., React Router, Redux, Zustand, Axios).
Imperative (Step-by-step instructions):
"Go down to the lobby, walk out the front door, turn right, walk 3 blocks, and stop."
Declarative (Result-oriented description):
"I want to be at the coffee shop at 3rd Avenue."React is declarative. You describe what the UI should look like for a given state, and React handles the updates to make the browser match that description. You don't write manual DOM selectors (document.getElementById) or append elements yourself.
Library vs. Framework Comparison
| Feature | React | Angular |
|---|---|---|
| Category | UI Library | Full Web Framework |
| Language | JS / JSX / TS | TypeScript |
| DOM Model | Virtual DOM (reconciliation) | Real DOM (incremental/Ivy engine) |
| Data Flow | Unidirectional (One-way) | Bidirectional (Two-way) |
| Core Philosophy | Plain JavaScript-first (CSS-in-JS, HTML-in-JS) | Separate HTML templates, CSS, and TS |
📋 Unidirectional Data Flow
In React, data flows in one direction: downward from parent components to child components via props. Children cannot update parent data directly; they must trigger callback functions passed down as props to request changes.
Which of the following statements is TRUE about React?
2. Virtual DOM & Reconciliation
Directly manipulating the browser's Real DOM is slow and expensive. When you change an element's text using the Real DOM, the browser has to recalculate the page layout and repaint the screen (a process called reflow and repaint). Doing this hundreds of times a second freezes the browser.
To solve this, React uses a Virtual DOM — a lightweight, in-memory copy of the Real DOM made of plain JavaScript objects.
The Reconciliation Process (Diffing)
When a component's state or props change:
- Render: React generates a new Virtual DOM tree representing the updated UI.
- Diffing: React compares this new Virtual DOM tree with the previous Virtual DOM tree using a highly optimized comparison algorithm.
- Reconciliation: React calculates the minimum number of changes required and updates only those specific modified nodes in the browser's Real DOM.
State Change
|
v
Render New Virtual DOM Tree
|
v
Diff with Old Virtual DOM Tree <--- (React's O(n) heuristic algorithm)
|
v
Reconcile (Batch update only modified nodes in Real DOM)💡 Reconciliation Rules
React's diffing algorithm relies on two assumptions to achieve O(n) complexity:
- Two elements of different types will produce different trees. If you change a
<div>to a<span>, React destroys the entire div subtree and builds the span subtree from scratch. - Lists of elements need unique, stable keys to help React identify which items were added, moved, or deleted across renders.
How does the Virtual DOM improve browser rendering performance?
3. JSX (JavaScript XML)
JSX is a syntax extension for JavaScript that allows you to write HTML-like markup directly inside JavaScript files. It combines UI structure and component logic in one place.
Browsers do not understand JSX. A compiler (like Babel or SWC) compiles JSX code into standard JavaScript calls (React.createElement or the modern jsx() runtime function) before rendering.
Compilation Example
// What you write (JSX)
const element = <h1 className="title">Hello World</h1>;
// What it compiles to (Plain JavaScript)
const element = React.createElement('h1', { className: 'title' }, 'Hello World');The Rules of JSX
To write valid JSX, you must follow three strict rules:
- Return a Single Root Element: You cannot return multiple sibling HTML tags. They must be wrapped in a single parent tag (like
<div>or a React Fragment<></>). Fragments allow you to group elements without adding extra wrapper nodes to the DOM. - Close All Tags: Every tag must be explicitly closed, including self-closing elements (e.g.,
<img />,<input />,<br />). - Use camelCase Attributes: Because JSX compiles into JavaScript, attributes are mapped to JS properties. HTML attributes become camelCase (e.g.,
classbecomesclassName,onclickbecomesonClick, andforbecomeshtmlFor).
// Valid JSX using a Fragment, self-closing tag, and camelCase properties
function ProfileCard({ name, avatarUrl }) {
return (
<>
<h2 className="profile-name">{name}</h2>
<img src={avatarUrl} alt={name} className="avatar-img" />
<hr />
</>
);
}⚠️ JavaScript Expressions inside JSX
You can write any valid JavaScript expression (variables, function calls, math, ternaries) inside JSX by wrapping it in curly braces { }. Do not put statements (like if or for loops) inside curly braces; use ternary operators or array map functions instead.
Which of the following is an invalid JSX snippet?
4. Components & Props
Components are the independent, reusable building blocks of a React user interface.
- Functional Components (Modern): Simple JavaScript functions that accept
propsand return JSX. They use Hooks to manage state and lifecycles. - Class Components (Legacy): ES6 classes extending
React.Component. They usethis.stateand lifecycle methods (likecomponentDidMount). They are considered legacy and should not be used in new code.
Props (Properties)
Props are read-only, immutable values passed from a parent component to a child component, behaving exactly like arguments passed to a function.
// Child Component: Receives props, destructures name and role
function UserCard({ name, role = 'Guest' }) {
return (
<div className="user-card">
<h3>{name}</h3>
<p>Role: {role}</p>
</div>
);
}
// Parent Component: Passes data down as attributes
function App() {
return (
<div className="app-container">
<UserCard name="Alice" role="Admin" />
<UserCard name="Bob" /> {/* role defaults to 'Guest' */}
</div>
);
}The children Prop
Every component receives a special prop called children. It contains whatever content is nested between the opening and closing tags of the component.
// Layout Component
function Box({ children }) {
return <div style={{ border: '2px solid black', padding: '10px' }}>{children}</div>;
}
// Usage
function App() {
return (
<Box>
<h2>Inside the Box</h2>
<p>This is rendered via props.children.</p>
</Box>
);
}📋 Props are Read-Only
A component must never modify its own props. If you need to change data in response to user actions, you must use State, not props.
What is the main difference between Props and State in React?
5. State Management (useState)
State represents a component's memory — data that changes over time in response to user input, network calls, or timers. When a component's state changes, React schedules a re-render of that component and its children.
useState Hook Syntax
import { useState } from 'react';
const [count, setCount] = useState(0);
// [currentValue, setterFunction] = useState(initialValue)State as a Snapshot
React state updates are not executed immediately; they are scheduled for the next render. Think of state like a snapshot of the UI at a specific point in time.
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // Output: 0! Not 1.
};
}Because count is a snapshot variable, its value remains 0 throughout the execution of handleClick. The updated value of 1 is only available during the next render cycle.
Functional Updates (Batching & Consecutive Updates)
React batches multiple state updates inside event handlers to prevent unnecessary re-renders. If you need to update a state variable multiple times in a row based on its previous value, pass an updater function to the setter:
const handleIncrement = () => {
// Correct way to queue consecutive state changes
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
// The count will increment by 3, not 1.
};⚠️ Never Mutate State Directly
Always update state using the setter function returned by useState. Directly modifying arrays or objects (e.g., myArray.push('new') or myObject.name = 'newName') does not trigger a re-render because the reference to the object has not changed. Always create a new copy:
// WRONG: state.push(newItem);
// RIGHT:
setItems([...items, newItem]); // Array spreading
setProfile({ ...profile, name: 'Alice' }); // Object spreadingWhat happens if you run setCount(count + 1) three times consecutively in a single event handler?
6. Component Lifecycle & Effects (useEffect)
The useEffect hook allows functional components to perform side effects (such as data fetching, manual DOM manipulation, setting up event listeners, or connecting to web sockets).
The Synchronization Mental Model
Do not think of useEffect in terms of class lifecycle hooks (componentDidMount, componentDidUpdate, componentWillUnmount). Instead, think of it as a way to synchronize your component with an external system based on state and props.
The Dependency Array
The behavior of useEffect is controlled by its second argument, the dependency array:
| Dependency Array | Running Behavior | Analogy |
|---|---|---|
No Dependency ArrayuseEffect(() => {}) | Runs after every single render of the component. | A reporter who writes a story on every minor event. |
Empty Dependency ArrayuseEffect(() => {}, []) | Runs only once, immediately after the component mounts. | A welcome sign that is only set up when the store opens. |
With DependenciesuseEffect(() => {}, [id]) | Runs on mount, and whenever the id value changes between renders. | A weather display that updates only when the location changes. |
The Cleanup Function
If your effect sets up an ongoing task (like a timer, a document event listener, or a web socket connection), you must return a cleanup function from the effect. React will execute this cleanup function before the component unmounts or before the effect runs again.
import { useState, useEffect } from 'react';
function MouseTracker() {
const [coords, setCoords] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setCoords({ x: e.clientX, y: e.clientY });
};
// 1. Setup side-effect
window.addEventListener('mousemove', handleMouseMove);
// 2. Return cleanup function to prevent memory leaks
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []); // Runs once on mount, cleans up on unmount
return <p>Mouse: {coords.x}, {coords.y}</p>;
}⚠️ Memory Leaks from Stale Closures
Failing to clean up event listeners or timers can lead to memory leaks, causing your app to slow down or crash as memory usage increases.
When does the cleanup function returned inside useEffect execute?
7. Ref Management (useRef)
useRef is a hook that provides a way to persist mutable values across renders without triggering a new render when the value changes. It is also used to reference DOM nodes directly.
useRef returns a mutable object with a single property: .current.
import { useRef } from 'react';
const myRef = useRef(initialValue); // Returns { current: initialValue }Common Use Cases
- Accessing DOM Elements Directly (e.g., focusing an input):
import { useRef } from 'react';
function AutoFocusInput() {
const inputRef = useRef(null);
const handleFocus = () => {
// Accesses the real DOM node and calls focus()
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>Focus Input</button>
</>
);
}- Storing Mutable Values without Re-rendering (e.g., tracking a timer ID):
import { useState, useRef } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
const timerId = useRef(null); // Holds the interval ID
const startTimer = () => {
if (timerId.current !== null) return;
timerId.current = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
};
const stopTimer = () => {
clearInterval(timerId.current);
timerId.current = null; // Does NOT trigger a re-render
};
}| Feature | State (useState) | Ref (useRef) |
|---|---|---|
| Triggers Re-render? | Yes | No |
| Persistence? | Persists across renders | Persists across renders |
| Primary Use Case | Data rendered on the screen | DOM refs, timer IDs, tracking renders |
Which of the following is true about updating a Ref (myRef.current = newValue)?
8. Performance Optimization Hooks
By default, when a parent component renders, all of its child components render recursively, regardless of whether their props changed. React offers optimization hooks to prevent unnecessary work.
React.memo
React.memo is a higher-order component that wraps a functional component. It performs a shallow comparison of the component's props and prevents a re-render if the props have not changed.
import React from 'react';
const ChildComponent = React.memo(({ name }) => {
console.log('Child rendered!');
return <p>{name}</p>;
});useMemo
useMemo caches (memoizes) the result of an expensive calculation so that it doesn't have to be recalculated on every render unless its dependencies change.
import { useMemo } from 'react';
const expensiveResult = useMemo(() => {
return runHeavyCalculation(data);
}, [data]); // Recalculates only when 'data' changesuseCallback
useCallback caches a function definition itself between renders.
When you pass a function to a memoized child component, the parent creates a new function instance on every render, which breaks the child's React.memo optimization. Wrapping the parent function in useCallback keeps the function reference identical.
import { useCallback } from 'react';
const handleAction = useCallback(() => {
doSomething(id);
}, [id]); // Returns the same function reference unless 'id' changes📋 Premature Optimization Caution
Do not wrap every function and calculation in these hooks. They have performance overhead (maintaining dependency arrays, checking caches). Use them only when a component is demonstrably slow or experiences rendering bottlenecks.
Why would passing a standard callback function const handleClick = () => {} break React.memo on a child component?
9. Advanced State & Context
As your application grows, passing props down through multiple layers of components (called Prop Drilling) becomes difficult to manage.
Context API (useContext)
Context allows you to share global data (like themes, user info, or languages) across the entire component tree without passing props manually at every level.
Prop Drilling: Parent -> Child -> SubChild -> Target (Props passed through all)
Context API: Parent [Context Provider] -------------> Target [useContext]Context API Example
import { createContext, useContext, useState } from 'react';
// 1. Create the Context
const ThemeContext = createContext(null);
// 2. Provider Component wrapping the tree
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Child Component consuming context directly
function ThemeButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Current Theme: {theme}
</button>
);
}useReducer
For components with complex state logic (e.g., state with multiple sub-values or state transitions dependent on previous actions), useReducer is preferred over useState.
import { useReducer } from 'react';
// 1. Define reducer function containing state transition rules
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
default: throw new Error();
}
}
// 2. Usage inside component
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}What problem is the Context API primarily designed to solve?
10. Lists & Keys
When rendering lists of components from an array, you must assign a unique key prop to each item.
const tasks = [
{ id: 't1', text: 'Learn React' },
{ id: 't2', text: 'Build a project' }
];
function TaskList() {
return (
<ul>
{tasks.map(task => (
// Key prop is required here
<li key={task.id}>{task.text}</li>
))}
</ul>
);
}Why are Keys important?
Keys act as identifiers for elements in a list. They help React identify which items have changed, been added, or been removed in the array. This allows React to move DOM nodes instead of destroying and re-creating them from scratch during reconciliation.
⚠️ The Index-As-Key Anti-pattern
Avoid using the array index as a key (e.g., key={index}). If the list is sorted, filtered, or has elements inserted/removed, the indices of items will change. This causes React to misidentify items, leading to UI rendering bugs (such as input field text mismatch) and degraded performance. Always use stable, unique IDs from your data source.
Why is using an array index as a key considered bad practice in React list rendering?
11. Forms: Controlled vs. Uncontrolled
Forms in React handle input elements in one of two ways:
- Controlled Components: The input's current value is driven by React State. The state is the single source of truth for the input.
- Uncontrolled Components: The input's value is handled by the browser DOM itself. React accesses the input's current value on-demand using a
ref.
Controlled vs Uncontrolled Examples
// 1. Controlled Component (State-driven)
function ControlledForm() {
const [name, setName] = useState('');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
// 2. Uncontrolled Component (DOM-driven)
import { useRef } from 'react';
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
alert('Submitted value: ' + inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} type="text" />
<button type="submit">Submit</button>
</form>
);
}💡 Which should you use?
Use Controlled Components for almost all standard form scenarios. They make features like real-time validation, input formatting (e.g., auto-formatting phone numbers), and dynamic button disabling simple. Uncontrolled components are useful for simple forms with minimal logic or when integrating with non-React libraries.
Which component type maintains form values in React state as the single source of truth?
12. Routing (React Router)
React Router is the standard library used to handle client-side routing in React SPAs, allowing navigation without page reloads.
Routing Configuration Example
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';
function UserProfile() {
// Accessing route parameters (e.g., /user/42 -> userId = 42)
const { userId } = useParams();
return <h2>Profile of User: {userId}</h2>;
}
export default function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link> | <Link to="/user/123">My Profile</Link>
</nav>
<Routes>
<Route path="/" element={<h1>Home Page</h1>} />
<Route path="/user/:userId" element={<UserProfile />} />
</Routes>
</BrowserRouter>
);
}Lazy Loading Routes
To optimize initial page load times, you can split your Javascript bundle by lazy-loading route components using React.lazy and wrapping the routes in a <Suspense> boundary.
import React, { Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Lazy load the component
const HeavyDashboard = React.lazy(() => import('./HeavyDashboard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading Page...</div>}>
<Routes>
<Route path="/dashboard" element={<HeavyDashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}Which component is required to wrap lazy-loaded components and display a loading fallback UI?
13. Rules of Hooks & Common Gotchas
Hooks are JavaScript functions, but they enforce two strict rules that must be followed for React to execute them correctly:
- Only Call Hooks at the Top Level: Do not call hooks inside loops, conditional statements (
if), or nested functions. Hooks must be called in the exact same order on every render. - Only Call Hooks from React Functions: Call hooks only from React functional components or custom hooks. Do not call them from plain JavaScript functions.
Common Gotchas to Avoid
1. Infinite Loop in useEffect
The Gotcha: Modifying a state variable inside useEffect while listing that same variable in the dependency array:
useEffect(() => {
setCount(count + 1); // Triggers re-render, running the effect again -> Infinite Loop!
}, [count]);The Solution: Correctly configure your dependency array or use a functional update that does not depend on the outer state variable.
2. Stale Closures in Effects/Callbacks
The Gotcha: Referencing a state variable inside useEffect or useCallback without declaring it in the dependency array. The hook remembers the variable value from when it was first declared (stale reference).
The Solution: Add the referenced state variables to the dependency array.
💡 Key Facts to Remember
- React is a Library, not a Framework.
- Reconciliation is the sync between Virtual DOM and Real DOM.
- JSX attributes use camelCase (e.g.
className,htmlFor). - Hooks must always start with the prefix
use(e.g.useState,useMyHook). - Class components are legacy; functional components with Hooks are modern best practice.