What Is React?
React is a JavaScript library for building user interfaces. Created by Meta (formerly Facebook) and open-sourced in 2013, it has become the most widely adopted UI library in the frontend ecosystem. React lets you compose complex UIs from small, isolated pieces of code called components.
React's core ideas:
- Declarative — describe what the UI should look like, React handles the DOM updates
- Component-Based — build encapsulated components that manage their own state
- Unidirectional Data Flow — data flows down from parent to child via props
- Virtual DOM — React maintains an in-memory representation of the real DOM and syncs them efficiently
Before React, developers directly manipulated the DOM using jQuery or vanilla JavaScript. This was error-prone and hard to scale. React introduced a mental model where you describe the desired UI state and let the library figure out the minimal set of DOM changes needed.
Traditional DOM manipulation:
[User Action] --> [Find Element] --> [Modify Element] --> [Hope nothing breaks]
React's declarative model:
[State Change] --> [Describe UI] --> [React diffs VDOM] --> [Minimal DOM update]
Components
Components are the building blocks of React applications. Every React app is a tree of components, starting from a root component down to the smallest UI elements.
Function Components
Function components are the modern standard. They are plain JavaScript functions that accept props and return JSX.
// Function declaration
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Arrow function
const Greeting = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};
// Implicit return (single expression)
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;
Class Components (Legacy)
Class components were the original way to write components with state and lifecycle methods. You will still see them in older codebases, but new code should use function components with hooks.
import { Component } from 'react';
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
Function vs Class Components
| Feature | Function Components | Class Components |
|---|---|---|
| Syntax | Plain function | ES6 class extending Component |
| State | useState hook | this.state + this.setState |
| Lifecycle | useEffect hook | componentDidMount, etc. |
this keyword | Not needed | Required (bindings can be tricky) |
| Code length | Shorter | More boilerplate |
| Performance | Slightly better (no class overhead) | Slightly heavier |
| Current status | Recommended | Legacy (still supported) |
Component Rules
- Component names must start with a capital letter — lowercase names are treated as HTML elements
- Components must return JSX (or
null) - Components should be pure — same input, same output during render
- Side effects go in useEffect, not in the render body
- Components should have a single responsibility — do one thing well
Composing Components
Real applications are built by composing components into a tree. Each component manages its own piece of the UI.
function App() {
return (
<div>
<Header />
<main>
<Sidebar />
<Content>
<ArticleList />
</Content>
</main>
<Footer />
</div>
);
}
function Header() {
return (
<header>
<Logo />
<Navigation />
<SearchBar />
</header>
);
}
Component tree:
App
/ | \
Header Main Footer
/|\ / \
Logo Nav Sidebar Content
Search |
ArticleList
JSX In Depth
JSX is a syntax extension that looks like HTML but compiles to JavaScript function calls. It is not a template language — it has the full power of JavaScript.
// JSX
const element = <h1 className="title">Hello</h1>;
// Compiles to
const element = React.createElement('h1', { className: 'title' }, 'Hello');
With React 17+, a new JSX transform was introduced so you no longer need to import React at the top of every file. The compiler handles the transformation automatically.
JSX Rules
- Return a single root element (or use Fragments
<>...</>) - Close all tags:
<img />,<br /> - Use
classNameinstead ofclass - Use
htmlForinstead offor - Use camelCase for event handlers and DOM properties:
onClick,onChange,tabIndex - JavaScript expressions go inside
{curly braces}
Embedding Expressions
Any valid JavaScript expression can appear inside curly braces in JSX.
function UserGreeting({ user }) {
const formattedDate = new Date().toLocaleDateString();
return (
<div>
<h1>Welcome, {user.firstName + ' ' + user.lastName}</h1>
<p>Today is {formattedDate}</p>
<p>You have {user.messages.length} messages</p>
<p>Status: {user.isActive ? 'Active' : 'Inactive'}</p>
<img
src={user.avatar}
alt={`Avatar of ${user.firstName}`}
style={{ borderRadius: '50%', width: 64, height: 64 }}
/>
</div>
);
}
JSX Gotchas
// 1. Boolean, null, undefined render nothing
<div>{true}</div> // renders: <div></div>
<div>{null}</div> // renders: <div></div>
<div>{undefined}</div> // renders: <div></div>
// 2. Zero IS rendered (common bug with && operator)
<div>{0 && <Child />}</div> // renders: <div>0</div> (Bug!)
<div>{items.length > 0 && <Child />}</div> // correct
// 3. Objects cannot be rendered directly
<div>{{ name: 'Alice' }}</div> // Error: Objects are not valid as React child
// 4. Arrays are flattened
<div>{[1, 2, 3]}</div> // renders: <div>123</div>
Inline Styles
JSX inline styles use a JavaScript object with camelCased property names, not a CSS string.
// Wrong
<div style="color: red; font-size: 16px">Hello</div>
// Correct
<div style={{ color: 'red', fontSize: '16px' }}>Hello</div>
<div style={{ color: 'red', fontSize: 16 }}>Hello</div> // numbers default to px
Props
Props (short for "properties") are how you pass data from parent to child components. They are read-only — a child component should never modify the props it receives.
// Parent passes props
<UserProfile name="Alice" age={30} isAdmin={true} />
// Child receives props
function UserProfile({ name, age, isAdmin }) {
return (
<div>
<p>{name}, {age}</p>
{isAdmin && <span className="badge">Admin</span>}
</div>
);
}
Destructuring Props
There are multiple ways to access props:
// 1. Destructure in parameter (recommended)
function Button({ variant, size, children }) {
return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}
// 2. Access from props object
function Button(props) {
return (
<button className={`btn-${props.variant} btn-${props.size}`}>
{props.children}
</button>
);
}
// 3. Destructure with rest (spread remaining props)
function Button({ variant, size, children, ...rest }) {
return (
<button className={`btn-${variant} btn-${size}`} {...rest}>
{children}
</button>
);
}
// Usage: <Button variant="primary" size="lg" disabled onClick={fn}>Click</Button>
Default Props
function Button({ variant = 'primary', size = 'md', children }) {
return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}
// All of these work:
<Button>Click</Button> // variant='primary', size='md'
<Button variant="danger">Delete</Button> // variant='danger', size='md'
Children Prop
The special children prop contains whatever you put between the component's opening and closing tags.
function Card({ title, children }) {
return (
<div className="card">
<div className="card-header">{title}</div>
<div className="card-body">{children}</div>
</div>
);
}
// Usage
<Card title="Settings">
<p>Notification preferences</p>
<Toggle label="Email" />
<Toggle label="Push" />
</Card>
Passing Functions as Props (Callbacks)
Props are not limited to data. You can pass functions to let children communicate back to parents.
function Parent() {
const [items, setItems] = useState([]);
const handleAddItem = (item) => {
setItems(prev => [...prev, item]);
};
return (
<div>
<ItemList items={items} />
<AddItemForm onAdd={handleAddItem} />
</div>
);
}
function AddItemForm({ onAdd }) {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
onAdd({ id: Date.now(), text });
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">Add</button>
</form>
);
}
Data flow:
Parent (owns state)
| ^
| props | callback
v |
Child (receives & calls back)
Props vs State Comparison
| Props | State | |
|---|---|---|
| Who owns it? | Parent | The component itself |
| Can change? | No (read-only for receiver) | Yes (via setter function) |
| Triggers re-render? | Yes, when parent passes new value | Yes, when setter is called |
| Direction | Parent -> Child | Local to component |
| Use for | Configuring child behavior | Managing changing data |
State
State is data that changes over time. When state changes, React re-renders the component (and its children) to reflect the new state.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
State Update Rules
- Never mutate state directly — always use the setter function
- State updates are batched — multiple
setStatecalls in the same event handler trigger only one re-render - State updates may be asynchronous — you cannot rely on
statehaving the new value immediately after callingsetState - Use functional updates when new state depends on previous state
// Wrong — mutating state directly
const [user, setUser] = useState({ name: 'Alice', age: 30 });
user.age = 31; // NEVER do this
// Right — create a new object
setUser({ ...user, age: 31 });
// Or with functional update:
setUser(prev => ({ ...prev, age: prev.age + 1 }));
State Batching
React batches state updates that happen inside event handlers. This means multiple setState calls result in a single re-render.
function BatchExample() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
const handleClick = () => {
setCount(c => c + 1); // Does NOT re-render yet
setFlag(f => !f); // Does NOT re-render yet
// React re-renders ONCE here, after the handler finishes
};
console.log('Render'); // Logs once per click, not twice
return <button onClick={handleClick}>Update ({count})</button>;
}
Since React 18, batching also applies to promises, setTimeout, and native event handlers — not just React event handlers.
Component Lifecycle
Even though function components don't have explicit lifecycle methods like class components, they still follow a lifecycle. Understanding this lifecycle is essential for writing correct React code.
The Three Phases
MOUNTING UPDATING UNMOUNTING
(component (state/props change) (component removed
appears) from DOM)
| | |
v v v
[Render] ---> [Re-render on change] ---> [Cleanup]
[DOM update] [DOM update] [Remove from DOM]
[Effects run] [Cleanup prev effects]
[Effects run]
1. Mount Phase — The component is created and inserted into the DOM for the first time.
function MountExample() {
const [data, setData] = useState(null);
// This effect runs AFTER the first render (mount)
useEffect(() => {
console.log('Component mounted');
fetchData().then(setData);
}, []); // Empty dependency array = run only on mount
return <div>{data ? data.name : 'Loading...'}</div>;
}
2. Update Phase — The component re-renders because state or props changed.
function UpdateExample({ userId }) {
const [user, setUser] = useState(null);
// Runs on mount AND whenever userId changes
useEffect(() => {
console.log('Fetching user:', userId);
fetchUser(userId).then(setUser);
}, [userId]); // Re-runs when userId changes
return <div>{user?.name}</div>;
}
3. Unmount Phase — The component is removed from the DOM. Cleanup functions run.
function UnmountExample() {
useEffect(() => {
const interval = setInterval(() => {
console.log('tick');
}, 1000);
// This cleanup function runs when component unmounts
return () => {
console.log('Component unmounting, clearing interval');
clearInterval(interval);
};
}, []);
return <div>Timer running...</div>;
}
Lifecycle Comparison: Class vs Function
| Class Component | Function Component (Hooks) |
|---|---|
constructor() | useState(initialValue) |
componentDidMount() | useEffect(() => {}, []) |
componentDidUpdate() | useEffect(() => {}, [deps]) |
componentWillUnmount() | useEffect(() => { return () => cleanup }, []) |
shouldComponentUpdate() | React.memo() |
getDerivedStateFromProps() | Update state during render (rare) |
Re-rendering: When and Why
Understanding when React re-renders is critical for performance and correctness.
What Triggers a Re-render?
- State change — calling
setStatetriggers a re-render of that component - Parent re-renders — when a parent re-renders, all its children re-render by default
- Context change — when a context value changes, all consumers re-render
- Force update — calling
forceUpdate()(class) or using a workaround (rare)
What Does NOT Trigger a Re-render?
- Ref changes — updating
useRef().currentdoes not trigger a re-render - Variable changes — regular variables reset on every render; changing them has no effect
- Same state value — if you call
setStatewith the same value (by reference for objects), React bails out
function RenderDemo() {
const [count, setCount] = useState(0);
const renderCount = useRef(0);
renderCount.current += 1;
return (
<div>
<p>Count: {count}</p>
<p>Renders: {renderCount.current}</p>
<button onClick={() => setCount(c => c + 1)}>
Increment (causes re-render)
</button>
<button onClick={() => setCount(count)}>
Set same value (skips re-render)
</button>
</div>
);
}
Re-render Flow
setState called
|
v
Is new state === old state?
|
Yes | No
| |
v v
Bail Schedule re-render
out |
v
Call component function
|
v
Generate new JSX (virtual DOM)
|
v
Diff new VDOM vs old VDOM
|
v
Apply minimal DOM changes
|
v
Run useEffect cleanups (if deps changed)
|
v
Run useEffect callbacks (if deps changed)
Parent Re-render Cascades
When a parent re-renders, all children re-render by default, even if their props haven't changed. This is a common source of performance issues.
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
{/* ExpensiveChild re-renders every time Parent re-renders,
even though it receives no props from count */}
<ExpensiveChild />
</div>
);
}
// Fix: wrap with React.memo to skip re-render if props haven't changed
const ExpensiveChild = React.memo(function ExpensiveChild() {
console.log('ExpensiveChild rendered');
return <div>I am expensive to render</div>;
});
Fragments
Fragments let you group multiple elements without adding an extra DOM node. This is essential because JSX requires a single root element.
The Problem
// This won't work — multiple root elements
function Columns() {
return (
<td>Hello</td>
<td>World</td>
);
}
Solution 1: Fragment Syntax
import { Fragment } from 'react';
function Columns() {
return (
<Fragment>
<td>Hello</td>
<td>World</td>
</Fragment>
);
}
Solution 2: Short Syntax
function Columns() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
When You Need Fragment Over <>
The only difference: <Fragment> supports the key attribute. The short syntax <> does not.
function Glossary({ items }) {
return (
<dl>
{items.map(item => (
// You need Fragment here because you need the key prop
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
);
}
Why Fragments Matter
Without Fragment (adds unnecessary <div>):
<table>
<tr>
<div> <-- Invalid HTML! <div> inside <tr>
<td>A</td>
<td>B</td>
</div>
</tr>
</table>
With Fragment (clean DOM):
<table>
<tr>
<td>A</td> <-- Direct children, valid HTML
<td>B</td>
</tr>
</table>
Conditional Rendering
React doesn't have a special template syntax for conditionals. You use standard JavaScript operators.
Ternary Operator
Best when you need to render one thing or another.
function StatusBadge({ isOnline }) {
return (
<span className={isOnline ? 'badge-green' : 'badge-gray'}>
{isOnline ? 'Online' : 'Offline'}
</span>
);
}
Logical AND (&&)
Best when you want to render something or nothing.
function Notification({ message, isUrgent }) {
return (
<div>
{message && <p className="notification">{message}</p>}
{isUrgent && <span className="urgent-icon">!</span>}
</div>
);
}
Watch out for falsy values that render:
// Bug: renders "0" on screen when items is empty
{items.length && <ItemList items={items} />}
// Fix: use explicit boolean
{items.length > 0 && <ItemList items={items} />}
Early Return
Best when a condition should prevent the entire component from rendering.
function Dashboard({ user }) {
if (!user) {
return <p>Please log in.</p>;
}
if (user.isBanned) {
return <p>Account suspended.</p>;
}
return (
<div>
<h1>Welcome, {user.name}</h1>
<DashboardContent />
</div>
);
}
Variable Assignment
Best when the logic is complex and you want to keep JSX clean.
function PricingCard({ plan }) {
let badge;
if (plan === 'free') {
badge = <span className="badge-gray">Free</span>;
} else if (plan === 'pro') {
badge = <span className="badge-blue">Pro</span>;
} else {
badge = <span className="badge-gold">Enterprise</span>;
}
return (
<div className="card">
{badge}
<h2>{plan} Plan</h2>
</div>
);
}
Rendering Lists
Use .map() to render arrays. Always provide a unique key prop.
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id} className={todo.done ? 'completed' : ''}>
{todo.text}
</li>
))}
</ul>
);
}
Key Rules
- Keys must be unique among siblings (not globally unique)
- Keys should be stable — use IDs from your data, not random values
- Don't use array index as key if the list can be reordered, filtered, or items can be added/removed in the middle
- Keys are not passed as props to the component — if you need the ID inside the child, pass it separately
Why Keys Matter
Keys help React identify which items changed, were added, or removed. Without stable keys, React may re-use DOM elements incorrectly, causing bugs.
// Bad — index as key breaks when items reorder
{items.map((item, index) => (
<ListItem key={index} item={item} />
))}
// Good — stable unique ID
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
Rendering Nested Lists
function CategoryList({ categories }) {
return (
<div>
{categories.map(category => (
<section key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</section>
))}
</div>
);
}
Filtering Before Rendering
function ActiveUsers({ users }) {
const activeUsers = users.filter(user => user.isActive);
if (activeUsers.length === 0) {
return <p>No active users.</p>;
}
return (
<ul>
{activeUsers.map(user => (
<li key={user.id}>{user.name} — {user.role}</li>
))}
</ul>
);
}
Event Handling
React wraps native DOM events in SyntheticEvents for cross-browser compatibility. Events use camelCase naming.
function EventExamples() {
const handleClick = (e) => {
console.log('Button clicked', e.target);
};
const handleChange = (e) => {
console.log('Input value:', e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted');
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button type="button" onClick={handleClick}>Click me</button>
<button type="submit">Submit</button>
</form>
);
}
Passing Arguments to Event Handlers
function ItemList({ items, onDelete }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
{/* Wrap in arrow function to pass arguments */}
<button onClick={() => onDelete(item.id)}>Delete</button>
</li>
))}
</ul>
);
}
Common Events
| Event | HTML Equivalent | Use Case |
|---|---|---|
onClick | onclick | Buttons, links, clickable elements |
onChange | onchange | Inputs, selects, textareas |
onSubmit | onsubmit | Form submission |
onKeyDown | onkeydown | Keyboard shortcuts |
onFocus / onBlur | onfocus / onblur | Focus management |
onMouseEnter / onMouseLeave | onmouseenter / onmouseleave | Hover effects |
Controlled vs Uncontrolled Components
Controlled Components
The component's value is controlled by React state. This is the recommended approach for forms.
function ControlledForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log({ name, email });
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
}
Uncontrolled Components
The DOM itself manages the value. You use a ref to read the value when needed.
function UncontrolledForm() {
const nameRef = useRef(null);
const emailRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log({
name: nameRef.current.value,
email: emailRef.current.value,
});
};
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} placeholder="Name" />
<input ref={emailRef} placeholder="Email" />
<button type="submit">Submit</button>
</form>
);
}
Controlled vs Uncontrolled
| Controlled | Uncontrolled | |
|---|---|---|
| Value source | React state | DOM |
| Update mechanism | onChange + setState | DOM handles it |
| Read value | From state variable | Via ref.current.value |
| Validation | On every keystroke | On submit |
| Best for | Dynamic forms, live validation | Simple forms, file inputs |
Thinking in React: Building a Complete Feature
Let's walk through how to build a feature the "React way".
Task: Build a searchable product list.
Step 1: Break the UI into a component hierarchy
FilterableProductTable
|-- SearchBar
|-- ProductTable
|-- ProductCategoryRow
|-- ProductRow
Step 2: Build a static version first (no state)
function FilterableProductTable({ products }) {
return (
<div>
<SearchBar />
<ProductTable products={products} />
</div>
);
}
function SearchBar() {
return (
<form>
<input type="text" placeholder="Search..." />
<label>
<input type="checkbox" /> Only show in stock
</label>
</form>
);
}
function ProductTable({ products }) {
const categories = [...new Set(products.map(p => p.category))];
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{categories.map(category => (
<Fragment key={category}>
<ProductCategoryRow category={category} />
{products
.filter(p => p.category === category)
.map(product => (
<ProductRow key={product.id} product={product} />
))}
</Fragment>
))}
</tbody>
</table>
);
}
Step 3: Identify the minimal state
- Search text (changes based on user input)
- "In stock only" checkbox (changes based on user input)
- Product list — passed as props, NOT state
Step 4: Add state and wire it up
function FilterableProductTable({ products }) {
const [searchText, setSearchText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);
const filteredProducts = products.filter(product => {
const matchesSearch = product.name
.toLowerCase()
.includes(searchText.toLowerCase());
const matchesStock = !inStockOnly || product.inStock;
return matchesSearch && matchesStock;
});
return (
<div>
<SearchBar
searchText={searchText}
inStockOnly={inStockOnly}
onSearchChange={setSearchText}
onStockChange={setInStockOnly}
/>
<ProductTable products={filteredProducts} />
</div>
);
}
Step 5: State lives in the closest common ancestor of components that need it.
FilterableProductTable <-- state lives here (both children need it)
|-- SearchBar <-- writes to state (via callbacks)
|-- ProductTable <-- reads from state (via filtered props)
Common Mistakes
1. Mutating State
// WRONG — mutating array
const [items, setItems] = useState(['a', 'b']);
items.push('c'); // Mutation! React won't know to re-render
setItems(items); // Same reference — React skips update
// RIGHT — create new array
setItems([...items, 'c']);
2. Missing Key Prop
// WRONG — missing key
{items.map(item => <li>{item.name}</li>)}
// RIGHT — unique key
{items.map(item => <li key={item.id}>{item.name}</li>)}
3. Calling setState in Render
// WRONG — infinite loop
function Bad() {
const [count, setCount] = useState(0);
setCount(1); // Called during render -> triggers re-render -> called again -> ...
return <div>{count}</div>;
}
4. Using Stale State in Callbacks
// WRONG — stale closure
function Counter() {
const [count, setCount] = useState(0);
const incrementThree = () => {
setCount(count + 1); // count is 0
setCount(count + 1); // count is still 0
setCount(count + 1); // count is still 0 -> result: 1
};
// RIGHT — functional updates
const incrementThree = () => {
setCount(c => c + 1); // 0 -> 1
setCount(c => c + 1); // 1 -> 2
setCount(c => c + 1); // 2 -> 3
};
}
Interview Questions
Q1: What is the Virtual DOM and how does it work?
The Virtual DOM is a lightweight JavaScript representation of the actual DOM. When state changes, React creates a new Virtual DOM tree, compares it with the previous one (diffing), and applies only the minimal changes to the real DOM (reconciliation). This is faster than directly manipulating the DOM because batch updates reduce layout thrashing.
Q2: Why does React need keys in lists?
Keys give React a way to identify which items in a list have changed, been added, or removed. Without stable keys, React uses index by default, which can cause incorrect component reuse when items reorder. Keys should be stable, unique IDs from your data — not array indices.
Q3: What is the difference between props and state?
Props are external data passed from parent to child (read-only). State is internal data owned by the component (mutable via setter). Both trigger re-renders when they change. Props flow down the tree; state is local.
Q4: What happens when you call setState?
React schedules a re-render. The component function is called again, producing new JSX. React diffs the new virtual DOM against the old one and applies minimal DOM updates. Effects run after the DOM updates. If the new state is identical to the old state (by reference), React bails out and skips the re-render.
Q5: Why should components be pure?
Pure components produce the same output for the same input. This makes them predictable, testable, and allows React to optimize rendering (skip re-renders when props haven't changed). Side effects in render can cause infinite loops, stale data, and race conditions.
Q6: When should you use controlled vs uncontrolled components?
Use controlled components when you need real-time validation, conditional disabling, formatted input, or synchronized state across multiple fields. Use uncontrolled components for simple cases where you only need the value on submit, or for file inputs (which must be uncontrolled).
Q7: Explain React's reconciliation algorithm.
React uses a heuristic O(n) diffing algorithm:
- Elements of different types produce different trees (full rebuild)
- Elements of the same type are updated in place (only changed attributes)
- Keys identify child elements across renders (stable reordering)
This avoids the O(n^3) cost of generic tree diffing.
Q8: What are Fragments and why use them?
Fragments (<Fragment> or <>) let you return multiple elements without adding extra DOM nodes. This is important for valid HTML structure (e.g., table rows), CSS layouts (Flexbox/Grid direct children), and keeping the DOM clean. Use <Fragment> over <> when you need a key attribute.
Key Takeaways
- Components are reusable UI building blocks — compose them into trees
- JSX compiles to
React.createElement()calls — it's JavaScript, not HTML - Props flow down, state is local — this is unidirectional data flow
- Always use the setter function to update state — never mutate directly
- Lists need unique, stable
keyprops — not array indices - Understand the lifecycle: mount, update, unmount
- Re-renders cascade from parent to children — use
React.memoto optimize - Fragments avoid unnecessary DOM nodes
- Prefer controlled components for forms
- Think in React: break UI into components, find minimal state, lift state to common ancestor