Episode - React Complete Guide — Lifecycle, Hooks, Routing, Virtualization & More
Episode - React Complete Guide — Lifecycle, Hooks, Routing, Virtualization & More
Hey everyone! Welcome back to Namaste JavaScript. Today we cover React from the inside out — all the deep topics that separate a React beginner from a React expert!
This is your one-stop reference for all the advanced React concepts asked in interviews!
What we will cover:
- React Component Lifecycle (Class Components)
- useEffect vs useLayoutEffect
- shouldComponentUpdate and React.PureComponent
- ref and useRef
- BrowserRouter vs HashRouter
- React Window and React Virtualization
- useImperativeHandle Hook
- forEach vs for loop vs map — Differences
- useDispatch and useSelector (Redux Toolkit)
1. React Component Lifecycle
React class components go through 3 phases: Mounting, Updating, and Unmounting.
React Lifecycle — Visual:
==========================
MOUNTING (component appears on screen)
constructor()
↓
static getDerivedStateFromProps()
↓
render()
↓
componentDidMount() ← ✅ API calls, subscriptions go HERE
UPDATING (state or props change)
static getDerivedStateFromProps()
↓
shouldComponentUpdate() ← ✅ return false to skip re-render
↓
render()
↓
getSnapshotBeforeUpdate() ← capture scroll position before update
↓
componentDidUpdate() ← ✅ respond to prop/state changes
UNMOUNTING (component leaves screen)
componentWillUnmount() ← ✅ cleanup: clear timers, unsubscribe
// Full Lifecycle Example
class UserProfile extends React.Component {
// 1. CONSTRUCTOR — initialize state
constructor(props) {
super(props);
this.state = { user: null, loading: true };
console.log("1. constructor");
}
// 2. getDerivedStateFromProps — sync state from props
static getDerivedStateFromProps(props, state) {
console.log("2. getDerivedStateFromProps");
return null; // Return new state object or null
}
// 3. RENDER — pure, no side effects here!
render() {
console.log("3. render");
if (this.state.loading) return <p>Loading...</p>;
return <div>{this.state.user.name}</div>;
}
// 4. componentDidMount — ✅ BEST place for API calls
componentDidMount() {
console.log("4. componentDidMount");
fetch(`/api/users/${this.props.userId}`)
.then(r => r.json())
.then(user => this.setState({ user, loading: false }));
}
// 5. shouldComponentUpdate — prevent unnecessary re-renders
shouldComponentUpdate(nextProps, nextState) {
console.log("5. shouldComponentUpdate");
return nextState.user !== this.state.user; // Only update if user changed
}
// 6. getSnapshotBeforeUpdate — before DOM update
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("6. getSnapshotBeforeUpdate");
return null; // Passed to componentDidUpdate as snapshot
}
// 7. componentDidUpdate — after re-render
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("7. componentDidUpdate");
if (prevProps.userId !== this.props.userId) {
// userId changed — fetch new user!
this.fetchUser(this.props.userId);
}
}
// 8. componentWillUnmount — cleanup!
componentWillUnmount() {
console.log("8. componentWillUnmount");
// Clear timers, unsubscribe from websocket, cancel fetch
}
}
// Hooks equivalent in functional components:
// componentDidMount → useEffect(() => {}, [])
// componentDidUpdate → useEffect(() => {}, [dependency])
// componentWillUnmount → useEffect(() => { return () => cleanup; }, [])
Lifecycle → Hooks Mapping:
===========================
Class Lifecycle → Hooks Equivalent
────────────────────────────────────────────────
constructor → useState / useReducer initialization
componentDidMount → useEffect(() => {}, [])
componentDidUpdate → useEffect(() => {}, [dep])
componentWillUnmount → useEffect(() => { return cleanup; }, [])
shouldComponentUpdate → React.memo / useMemo
getDerivedStateFromProps → useState + useEffect + render logic
getSnapshotBeforeUpdate → useLayoutEffect (closest equivalent)
2. useEffect vs useLayoutEffect
Both run after render — but at different times in the browser's paint cycle!
The Difference — WHEN they run: ================================ Browser lifecycle after state change: React renders → React commits to DOM → [useLayoutEffect] → Browser PAINTS screen → [useEffect] useEffect → runs AFTER the browser paints the screen (async) useLayoutEffect → runs BEFORE the browser paints (sync, blocks paint)
// useEffect — DEFAULT CHOICE
// Runs asynchronously AFTER paint
// User sees the painted screen FIRST, then effect runs
useEffect(() => {
console.log("useEffect: runs AFTER paint");
document.title = "New Title"; // Safe to do after paint
}, []);
// useLayoutEffect — SPECIAL CASES ONLY
// Runs synchronously BEFORE paint
// Blocks the browser from painting until effect completes
useLayoutEffect(() => {
console.log("useLayoutEffect: runs BEFORE paint");
// Any DOM measurement/mutation here is reflected in the SAME paint
}, []);
// ORDER OF EXECUTION — with both:
function App() {
useLayoutEffect(() => console.log("A: useLayoutEffect"), []);
useEffect(() => console.log("B: useEffect"), []);
return <div>Hello</div>;
}
OUTPUT:
A: useLayoutEffect ← runs first, before browser paints
B: useEffect ← runs after browser paints
// The user sees "Hello" on screen between A and B!
WHEN TO USE useLayoutEffect:
=============================
1. Reading DOM dimensions (width, height, scroll position)
BEFORE the user sees the screen
const [height, setHeight] = useState(0);
useLayoutEffect(() => {
// Measure BEFORE paint → no flicker!
const h = ref.current.getBoundingClientRect().height;
setHeight(h);
// If you used useEffect, user would briefly see wrong height!
}, []);
2. Animations that require DOM measurements:
useLayoutEffect(() => {
const width = elementRef.current.offsetWidth;
// Set initial animation state based on DOM size
gsap.from(elementRef.current, { x: -width, duration: 0.3 });
}, []);
3. Syncing scroll position / focus
WHEN TO USE useEffect (most cases):
=====================================
- API calls (fetching data)
- Setting up subscriptions / websockets
- Timer setup (setTimeout, setInterval)
- Logging
- Any non-DOM-related side effects
- Setting document.title
RULE: Default to useEffect. Switch to useLayoutEffect only when
you see visual flickering or need DOM measurements before paint.
3. shouldComponentUpdate and React.PureComponent
By default React re-renders a component whenever its parent re-renders — even if props haven't changed. shouldComponentUpdate lets you control this!
// Default React behavior — re-renders on every parent render!
class Child extends React.Component {
render() {
console.log("Child rendered!"); // Logs even if props didn't change!
return <div>{this.props.name}</div>;
}
}
// shouldComponentUpdate — return false to SKIP re-render
class Child extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Only re-render if 'name' prop actually changed!
return nextProps.name !== this.props.name;
}
render() {
console.log("Child rendered!"); // Only logs when name changes
return <div>{this.props.name}</div>;
}
}
// React.PureComponent — automatic shallow comparison!
// Does shallow comparison of ALL props and state
// re-renders ONLY if any prop/state has changed (by reference)
class Child extends React.PureComponent {
render() {
// Automatically skips re-render if props haven't changed!
return <div>{this.props.name}</div>;
}
}
// PureComponent does:
// nextProps.name === this.props.name → skip render
// nextProps.name !== this.props.name → re-render
// It does SHALLOW comparison — won't work for nested objects!
// Hooks equivalent — React.memo for functional components
const Child = React.memo(function Child({ name }) {
console.log("Child rendered!");
return <div>{name}</div>;
});
// Custom comparison with React.memo
const Child = React.memo(
function Child({ user }) {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
// Return TRUE to SKIP re-render (opposite of shouldComponentUpdate!)
return prevProps.user.id === nextProps.user.id;
}
);
Summary:
─────────────────────────────────────────────
shouldComponentUpdate → class, manual, return false = skip
PureComponent → class, automatic shallow compare
React.memo → functional, automatic shallow compare
React.memo + comparator → functional, manual deep compare
4. ref and useRef
ref gives you direct access to a DOM element OR lets you store a mutable value that does NOT trigger a re-render when changed!
// 2 main uses of useRef:
// 1. Accessing DOM elements directly
// 2. Storing mutable values WITHOUT causing re-renders
// ─── USE 1: DOM ACCESS ───────────────────────────────
function FocusInput() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus(); // Direct DOM access!
inputRef.current.style.border = "2px solid blue";
}
return (
<div>
<input ref={inputRef} type="text" placeholder="I will get focused!" />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
// ─── USE 2: MUTABLE VALUE (no re-render) ─────────────
function Timer() {
const [count, setCount] = useState(0);
const timerRef = useRef(null); // Stores timer ID — doesn't cause re-render!
function start() {
timerRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
}
function stop() {
clearInterval(timerRef.current); // Uses stored timer ID!
}
return (
<div>
<p>Count: {count}</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
// KEY INSIGHT: Changing ref.current does NOT trigger re-render
// vs useState: changing state DOES trigger re-render
// useRef vs useState:
//
// useState:
// - Changing value → triggers re-render
// - Use for UI values that need to display
//
// useRef:
// - Changing .current → NO re-render
// - Use for: timer IDs, previous values, DOM elements
// Storing PREVIOUS value with useRef
function Counter() {
const [count, setCount] = useState(0);
const prevCount = useRef(count);
useEffect(() => {
prevCount.current = count; // Update AFTER render
});
return (
<p>Now: {count} | Before: {prevCount.current}</p>
);
}
// Class component equivalent:
// createRef() → creates a new ref object each render (in class constructor)
// useRef() → persists the same ref object across renders
class MyInput extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
focus() {
this.inputRef.current.focus();
}
render() {
return <input ref={this.inputRef} />;
}
}
5. BrowserRouter vs HashRouter
Both are from React Router — but they handle URLs very differently!
BrowserRouter uses REAL URLs: https://myapp.com/about https://myapp.com/users/123 HashRouter uses HASH URLs: https://myapp.com/#/about https://myapp.com/#/users/123
BrowserRouter — How it works:
================================
Uses the HTML5 History API (pushState, replaceState).
The URL looks clean — no # symbol.
The server MUST be configured to serve index.html for ALL routes!
// If user directly visits https://myapp.com/about
// The server needs to return index.html for /about
// Otherwise → 404 Not Found!
// Nginx config example:
// location / {
// try_files $uri /index.html;
// }
// Apache .htaccess:
// RewriteRule ^ /index.html [L]
import { BrowserRouter, Route, Routes } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<User />} />
</Routes>
</BrowserRouter>
);
}
HashRouter — How it works:
============================
Uses the URL hash (#) — the part after # is NOT sent to the server.
Browser handles everything — server always receives the base URL.
NO server configuration needed!
// User visits https://myapp.com/#/about
// Server receives request for: https://myapp.com/ (the base URL)
// Browser reads "#/about" and React Router renders <About>
// Server never knows about /about at all!
import { HashRouter, Route, Routes } from "react-router-dom";
function App() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</HashRouter>
);
}
Feature BrowserRouter HashRouter
──────────────────────────────────────────────────────────────
URL style /about (clean) /#/about (ugly)
Server config Required (serve index.html) Not required
SEO ✅ Better (clean URLs) ❌ Worse (# not indexed well)
Direct URL Works with server config Always works
Use case Production apps Static hosting, GitHub Pages
History API HTML5 pushState URL hash
Browser support Modern browsers All browsers (even IE!)
WHEN TO USE:
BrowserRouter → Production web apps with server config (Nginx, Apache, Express)
HashRouter → GitHub Pages, S3 static hosting, Electron apps, environments
where you can't control server-side routing
6. React Window and React Virtualization
When you render thousands of items in a list, the browser creates thousands of DOM nodes — this is SLOW. Virtualization renders only the visible items!
The Problem — Rendering 10,000 rows:
======================================
// ❌ BAD — renders ALL 10,000 <div> elements in the DOM!
function HugeList({ items }) {
return (
<div>
{items.map(item => (
<div key={item.id} className="row">
{item.name}
</div>
))}
</div>
);
}
// 10,000 DOM nodes = SLOW render, SLOW scroll, HIGH memory!
// Even if the user can only SEE 15 items at a time!
The Solution — Virtual Rendering: =================================== Only render what's VISIBLE in the viewport. As user scrolls, swap out items — render new ones, remove old ones. The DOM has only ~20-30 items at any time, regardless of list size! ┌─────────────────────────────────┐ │ Item 1 ← visible (rendered) │ │ Item 2 ← visible (rendered) │ │ Item 3 ← visible (rendered) │ │ Item 4 ← visible (rendered) │ │ Item 5 ← visible (rendered) │ └─────────────────────────────────┘ Item 6 to 10,000 — NOT in DOM! (just empty space)
// ✅ Using react-window (recommended — smaller, faster)
import { FixedSizeList } from "react-window";
const ROW_HEIGHT = 50;
function Row({ index, style }) {
return (
<div style={style}> {/* style contains position — REQUIRED! */}
Item {index + 1}: {items[index].name}
</div>
);
}
function VirtualList({ items }) {
return (
<FixedSizeList
height={400} // Visible area height in px
width="100%" // Width
itemCount={items.length} // Total item count
itemSize={ROW_HEIGHT} // Height of each row
>
{Row}
</FixedSizeList>
);
}
// Even with 100,000 items → DOM always has ~12 rows rendered!
// Buttery smooth scrolling at 60fps!
// Variable size items — VariableSizeList
import { VariableSizeList } from "react-window";
const getItemSize = index => {
return items[index].isExpanded ? 100 : 50; // Different heights!
};
function VirtualList({ items }) {
return (
<VariableSizeList
height={400}
width="100%"
itemCount={items.length}
itemSize={getItemSize} // Function instead of fixed number
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</VariableSizeList>
);
}
// FixedSizeList → all rows same height (products, contacts)
// VariableSizeList → rows have different heights (social feed, comments)
// FixedSizeGrid → 2D grid virtualization (spreadsheet)
// VariableSizeGrid → 2D grid with variable cell sizes
react-window vs react-virtualized: ===================================== Feature react-window react-virtualized ──────────────────────────────────────────────────────────── Bundle size ~5KB (small!) ~28KB (larger) API Simpler More features Performance Faster Slightly slower Maintainer Same author (bvaughn) Grid support ✅ Yes ✅ Yes Infinite scroll With react-window-infinite-loader Masonry layout ❌ ✅ Yes Recommendation: Use react-window for most cases. Use react-virtualized only if you need masonry or more built-in features. WHEN TO VIRTUALIZE: - Lists with 100+ items that may grow - Tables with many rows - Chat messages, social feeds - File explorers / tree views - Any scrollable container with dynamic content
7. useImperativeHandle Hook
useImperativeHandle lets a child component expose specific methods/values to its parent via a ref. Used with forwardRef!
The Problem: Parent wants to call child's internal methods. Parent → "Hey child, please focus your input!" Normal way: parent can't access child's internal DOM directly. useImperativeHandle: child EXPOSES what the parent can call!
// Step 1: Child wraps itself with forwardRef
// Step 2: useImperativeHandle exposes custom API via the ref
import { forwardRef, useImperativeHandle, useRef } from "react";
// ─── CHILD COMPONENT ─────────────────────────────────
const FancyInput = forwardRef(function FancyInput(props, ref) {
const inputRef = useRef(null);
// Expose ONLY these methods to parent — nothing else!
useImperativeHandle(ref, () => ({
focus() {
inputRef.current.focus();
},
clear() {
inputRef.current.value = "";
},
getValue() {
return inputRef.current.value;
}
}));
return (
<input
ref={inputRef}
type="text"
style={{ border: "2px solid blue", padding: "8px" }}
{...props}
/>
);
});
// ─── PARENT COMPONENT ────────────────────────────────
function Form() {
const inputRef = useRef(null);
return (
<div>
<FancyInput ref={inputRef} placeholder="Type here..." />
<button onClick={() => inputRef.current.focus()}>
Focus
</button>
<button onClick={() => inputRef.current.clear()}>
Clear
</button>
<button onClick={() => alert(inputRef.current.getValue())}>
Get Value
</button>
</div>
);
}
// Parent has access to: focus(), clear(), getValue()
// Parent does NOT have access to: inputRef.current (the real DOM node)
// Child controls what it exposes!
// With dependency array — recompute when deps change
useImperativeHandle(ref, () => ({
scrollToBottom() {
listRef.current.scrollTop = listRef.current.scrollHeight;
}
}), []); // Empty array → imperative handle created once
WHEN TO USE:
- Custom input components with focus/blur/scroll methods
- Reusable form components exposing validate() or reset()
- Video/Audio players exposing play(), pause(), seek()
- Modal components exposing open() and close()
- Scroll containers exposing scrollTo()
AVOID: Using it to expose state — use callbacks/props for that.
useImperativeHandle is for IMPERATIVE DOM operations only.
8. forEach vs for loop vs map — Differences
const numbers = [1, 2, 3, 4, 5];
// ─── for loop ─────────────────────────────────────────
// Traditional, most flexible, works on arrays AND non-arrays
// Can break out early with 'break'!
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] === 3) break; // ✅ Can STOP early!
console.log(numbers[i]);
}
OUTPUT: 1 2
// for...of (modern, cleaner for loop)
for (const num of numbers) {
if (num === 3) break; // ✅ Can STOP early!
console.log(num);
}
OUTPUT: 1 2
// ─── forEach ─────────────────────────────────────────
// Iterates array, runs callback for each item
// Returns undefined — cannot break or return early!
numbers.forEach(function(num) {
// ❌ 'break' is NOT allowed here!
// ❌ 'return' only exits the CALLBACK, not forEach
console.log(num);
});
OUTPUT: 1 2 3 4 5
// ─── map ─────────────────────────────────────────────
// Transforms each element and returns a NEW array
// NEVER mutates the original array
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] ← UNCHANGED!
Feature for loop forEach map
─────────────────────────────────────────────────────────────
Returns nothing undefined NEW array
Mutates orig No No No (returns new)
Break/continue ✅ Yes ❌ No ❌ No
Async/await ✅ Works ⚠️ Tricky ⚠️ Tricky
Works on Array, string Array only Array only
Performance Fastest Similar Similar
Use case General loop Side effects Transform values
// ⚠️ async/await with forEach — BROKEN!
async function processAll(items) {
// ❌ forEach does NOT await each callback!
items.forEach(async (item) => {
await processItem(item); // All run in PARALLEL — not sequential!
});
console.log("Done!"); // Prints BEFORE processing is done! 😱
}
// ✅ Use for...of for sequential async
async function processAll(items) {
for (const item of items) {
await processItem(item); // Waits for each one ✅
}
console.log("Done!"); // Prints AFTER all are done ✅
}
// ✅ Use Promise.all for parallel async
async function processAll(items) {
await Promise.all(items.map(item => processItem(item)));
console.log("Done!"); // Prints after ALL complete in parallel ✅
}
// React — forEach vs map:
// In JSX, you MUST use map (it returns the array of elements)
// forEach returns undefined — JSX can't render undefined!
// ❌ Wrong — forEach returns undefined, nothing renders!
function List({ items }) {
return <ul>{items.forEach(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
// ✅ Correct — map returns an array of JSX elements!
function List({ items }) {
return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
RULE:
for loop → when you need to break early OR iterate non-arrays
forEach → when you have side effects (logging, DOM updates) and don't need result
map → when you want a NEW transformed array (especially in React JSX)
9. useDispatch and useSelector (Redux Toolkit)
These two hooks connect React components to the Redux store — replacing the old connect() HOC pattern!
Redux Store Overview:
======================
┌──────────────────────────────────────────────┐
│ REDUX STORE │
│ │
│ State: { counter: 0, user: null } │
│ │
│ Reducers handle actions to update state │
└──────────────────────────────────────────────┘
↑ dispatch(action) ↓ select(state)
useDispatch() useSelector()
↑ ↓
┌─────────────────────────────────────────────┐
│ React Component │
│ const dispatch = useDispatch() │
│ const count = useSelector(s => s.counter) │
└─────────────────────────────────────────────┘
// STEP 1: Create Redux slice with Redux Toolkit
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
decrement: (state) => { state.value -= 1; },
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
// Auto-generated action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// STEP 2: Configure the store
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";
export const store = configureStore({
reducer: {
counter: counterReducer
}
});
// STEP 3: Provide store to React app
import { Provider } from "react-redux";
import { store } from "./store";
function Root() {
return (
<Provider store={store}>
<App />
</Provider>
);
}
// STEP 4: Use useSelector and useDispatch in components
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, incrementByAmount } from "./counterSlice";
function Counter() {
// useSelector — READ state from store
// Only re-renders when 'counter.value' changes!
const count = useSelector(state => state.counter.value);
// useDispatch — get the dispatch function
const dispatch = useDispatch();
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>
+1
</button>
<button onClick={() => dispatch(decrement())}>
-1
</button>
<button onClick={() => dispatch(incrementByAmount(10))}>
+10
</button>
</div>
);
}
// useSelector — Advanced Usage
// ✅ Select only what you need (fine-grained subscription)
const userName = useSelector(state => state.user.name);
// Only re-renders when user.name changes
// ✅ Select multiple values — object selector
const { name, email } = useSelector(state => ({
name: state.user.name,
email: state.user.email
}));
// WARNING: object selector creates new reference every render!
// Use shallowEqual to fix:
import { shallowEqual } from "react-redux";
const { name, email } = useSelector(state => ({
name: state.user.name,
email: state.user.email
}), shallowEqual); // Only re-renders if name OR email changes
// ✅ Memoized selector with createSelector (Reselect)
import { createSelector } from "@reduxjs/toolkit";
const selectActiveUsers = createSelector(
[state => state.users.list],
(users) => users.filter(u => u.isActive)
// Only recomputes if users.list changes — memoized!
);
const activeUsers = useSelector(selectActiveUsers);
// useDispatch — Usage Tips
const dispatch = useDispatch();
// Dispatching an action
dispatch(increment());
// Dispatching with payload
dispatch(incrementByAmount(5));
// Async action (thunk)
dispatch(fetchUserById(42)); // Works with Redux Thunk (included in RTK)
useSelector vs connect():
===========================
// Old Way — connect() HOC
const mapStateToProps = state => ({ count: state.counter.value });
const mapDispatchToProps = { increment, decrement };
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
// New Way — hooks (much simpler!)
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
// ... use dispatch(increment()) directly
}
Why hooks are better:
✅ Less boilerplate
✅ No HOC wrapping
✅ Easier to read
✅ TypeScript friendly
✅ Can use multiple selectors with fine-grained updates
Quick Recap
| Topic | Key Takeaway |
|---|---|
| Lifecycle | Mount → componentDidMount | Update → componentDidUpdate | Unmount → componentWillUnmount |
| useEffect | Runs AFTER browser paints. Default choice for all side effects. |
| useLayoutEffect | Runs BEFORE browser paints. Use only for DOM measurements to avoid flicker. |
| shouldComponentUpdate | Return false to skip re-render. Functional equivalent: React.memo |
| ref / useRef | Access DOM directly OR store mutable value WITHOUT triggering re-render |
| BrowserRouter | Clean URLs (/about). Needs server config. Better for SEO. |
| HashRouter | Hash URLs (/#/about). No server config needed. Good for static hosting. |
| react-window | Virtualize long lists — only render visible items. ~5KB bundle. |
| useImperativeHandle | Expose child's internal methods to parent via ref. Used with forwardRef. |
| map vs forEach | map returns new array (use in JSX). forEach returns undefined (side effects). |
| useSelector | Read data from Redux store. Re-renders only when selected data changes. |
| useDispatch | Send actions to Redux store to trigger state changes. |
Key Points to Remember
- componentDidMount = API calls | componentWillUnmount = cleanup
- useEffect by default — switch to useLayoutEffect only if you see visual flicker
- React.memo for functional = PureComponent for class
- useRef.current change does NOT re-render — unlike useState
- BrowserRouter for production with server | HashRouter for GitHub Pages/S3
- Virtualize any list with 100+ items using react-window's FixedSizeList
- useImperativeHandle + forwardRef always go together
- forEach with async/await is broken — use for...of instead
- JSX needs map, not forEach — map returns array, forEach returns undefined
- useSelector with shallowEqual to prevent unnecessary re-renders on object selectors
Keep coding, keep learning! See you in the next one!
Post a Comment