๐Ÿ”„ React Component Lifecycle

Class Components se Hooks tak ka safar - Interactive Learning

๐Ÿ“š Class Lifecycle Methods

Samjhiye: Purane zamane mein React mein class components use hote the, aur har component ki ek "life" hoti thi - birth (mounting), growth (updating), aur death (unmounting). Har stage pe kuch special methods call hote the jinhe hum lifecycle methods kehte hain.
Technical Definition: Lifecycle methods are special methods in class components that get automatically invoked at specific phases of a component's existence in the React component tree.

๐ŸŽญ Interactive Lifecycle Demo

๐ŸŒฑ Mounting

Birth

๐Ÿ”„ Updating

Growth

๐Ÿ’€ Unmounting

Death

Component Status: Not Mounted

0
๐Ÿ“‹ Lifecycle logs will appear here...
Dekho kya ho raha hai: Jab aap "Mount" button dabate ho, component DOM mein add hota hai. "Update" se component re-render hota hai, aur "Unmount" se component DOM se hata diya jata hai. Har action pe different lifecycle methods call hote hain!

๐Ÿ“Š Class Lifecycle Methods Table

Phase Method Purpose
Mounting constructor() Initialize state aur bind methods
static getDerivedStateFromProps() Props se state derive karna
render() JSX return karna
componentDidMount() API calls, subscriptions setup
Updating static getDerivedStateFromProps() Props changes handle karna
shouldComponentUpdate() Re-render control karna
render() Updated JSX return karna
getSnapshotBeforeUpdate() DOM update se pehle info capture karna
componentDidUpdate() DOM updates ke baad operations
Unmounting componentWillUnmount() Cleanup: timers, subscriptions cancel
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; // 1๏ธโƒฃ Sabse pehle yeh call hota hai } componentDidMount() { // 2๏ธโƒฃ Component DOM mein add hone ke baad // Yahan API calls karo console.log('Component mounted!'); } componentDidUpdate(prevProps, prevState) { // 3๏ธโƒฃ Jab bhi component update ho if (prevState.count !== this.state.count) { console.log('Count changed!'); } } componentWillUnmount() { // 4๏ธโƒฃ Component remove hone se pehle // Cleanup karo: timers, subscriptions console.log('Component will unmount!'); } render() { return <div>{this.state.count}</div>; } }

๐Ÿ”„ Why Lifecycle Changed in React 16+

Kahani: React 16 mein ek bada change aaya - Fiber Architecture. Isse React ko rendering ko chhote pieces mein todne ki power mili. Lekin purane lifecycle methods iske saath compatible nahi the, kyunki kuch methods multiple times call ho sakte the ya beech mein pause ho sakte the!
Technical Definition: React Fiber introduced asynchronous rendering, making some lifecycle methods unsafe because they could be called multiple times or interrupted mid-execution, leading to bugs and inconsistent behavior.

โš ๏ธ Deprecated Methods (Ab use mat karo!)

Ye methods ab UNSAFE hain:
  • โŒ componentWillMount() โ†’ UNSAFE_componentWillMount()
  • โŒ componentWillReceiveProps() โ†’ UNSAFE_componentWillReceiveProps()
  • โŒ componentWillUpdate() โ†’ UNSAFE_componentWillUpdate()

Kyun? Kyunki Fiber architecture mein ye methods multiple times call ho sakte hain, jisse side effects duplicate ho sakte hain!

โœ… New Safe Alternatives

Old (Unsafe) New (Safe) Reason
componentWillMount constructor / componentDidMount Side effects ko mount ke baad karo
componentWillReceiveProps getDerivedStateFromProps Pure function, no side effects
componentWillUpdate getSnapshotBeforeUpdate DOM info capture karne ke liye
๐Ÿ’ก Pro Tip: Modern React mein, class components ki jagah Hooks use karo! Hooks zyada simple aur powerful hain. useEffect hook sabhi lifecycle methods ko replace kar sakta hai!

๐ŸŽฃ Deep Dive into useEffect

useEffect kya hai? Ye ek React Hook hai jo functional components mein side effects handle karta hai. Side effects matlab - API calls, subscriptions, timers, DOM manipulation, etc. Ye class components ke componentDidMount, componentDidUpdate, aur componentWillUnmount ka combined replacement hai!
Technical Definition: useEffect is a Hook that lets you perform side effects in functional components. It runs after every render by default, but can be controlled with dependency arrays.

๐ŸŽฎ Interactive useEffect Demo

1๏ธโƒฃ No Dependencies (Har render pe)

0

Effect runs: 0 times

2๏ธโƒฃ Empty Dependencies (Sirf mount pe)

0

Effect runs: 0 times

3๏ธโƒฃ With Dependencies

Effect runs: 0 times

Dekho difference:
  • No Dependencies: Har baar component render ho, effect chalega
  • Empty Array []: Sirf pehli baar (mount pe) effect chalega
  • With Dependencies [name]: Jab 'name' change ho, tab effect chalega

๐Ÿ“ useEffect Syntax Variations

// 1๏ธโƒฃ Har render pe chalta hai (dangerous!) useEffect(() => { console.log('Runs after every render'); }); // 2๏ธโƒฃ Sirf mount pe chalta hai (componentDidMount) useEffect(() => { console.log('Runs only once on mount'); }, []); // Empty dependency array // 3๏ธโƒฃ Jab dependencies change hon (componentDidUpdate) useEffect(() => { console.log('Runs when count changes'); }, [count]); // Dependency array with count // 4๏ธโƒฃ Cleanup function ke saath (componentWillUnmount) useEffect(() => { const timer = setInterval(() => { console.log('Tick'); }, 1000); // Cleanup function - unmount pe ya next effect se pehle return () => { clearInterval(timer); console.log('Cleanup done!'); }; }, []);

๐Ÿงน Cleanup Function

Cleanup kyun zaroori hai? Imagine karo tumne ek timer start kiya, ya kisi API se subscription li. Agar component unmount ho jaye aur tum cleanup nahi karo, toh timer chalta rahega aur memory leak ho jayega! Cleanup function isi problem ko solve karta hai.

Timer Demo with Cleanup

Timer: 0s

โฑ๏ธ Timer logs will appear here...
function TimerComponent() { const [seconds, setSeconds] = useState(0); useEffect(() => { console.log('โฐ Timer started!'); const interval = setInterval(() => { setSeconds(prev => prev + 1); }, 1000); // ๐Ÿงน Cleanup function return () => { clearInterval(interval); console.log('๐Ÿ›‘ Timer stopped and cleaned up!'); }; }, []); // Empty array = sirf mount/unmount pe return <div>Seconds: {seconds}</div>; }

๐Ÿชค Dependency Array Traps

โš ๏ธ Common Mistakes:
  1. Missing Dependencies: Dependency array mein wo sab values dalo jo effect ke andar use ho rahi hain
  2. Object/Array Dependencies: Objects aur arrays har render pe naye bante hain, infinite loop ho sakta hai!
  3. Functions in Dependencies: Functions bhi har render pe naye bante hain
// โŒ WRONG: Missing dependency useEffect(() => { console.log(count); // 'count' use ho raha hai }, []); // But dependency array mein nahi hai! // โœ… CORRECT: Include all dependencies useEffect(() => { console.log(count); }, [count]); // 'count' dependency array mein hai // โŒ WRONG: Object dependency (infinite loop!) const user = { name: 'John' }; // Har render pe naya object useEffect(() => { console.log(user.name); }, [user]); // Infinite loop! // โœ… CORRECT: Use primitive values const user = { name: 'John' }; useEffect(() => { console.log(user.name); }, [user.name]); // Sirf name track karo

โ™พ๏ธ Avoiding Infinite Loops

Infinite loop kab hota hai? Jab tumhara effect kuch aisa change kare jo khud effect ko trigger kare! Example: effect mein state update karo, aur wo state dependency array mein ho, aur koi condition na ho.

Infinite Loop Demo (Controlled)

Loop Counter: 0

๐Ÿ”„ Loop logs will appear here...
// โŒ INFINITE LOOP! function BadComponent() { const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); // State update }, [count]); // count dependency mein hai! // Effect runs โ†’ count changes โ†’ effect runs โ†’ count changes โ†’ โ™พ๏ธ } // โœ… CORRECT: Add condition function GoodComponent() { const [count, setCount] = useState(0); useEffect(() => { if (count < 10) { // Condition add karo! setCount(count + 1); } }, [count]); }

๐ŸŒ Fetching Data Inside useEffect

API calls kaise karein? useEffect ke andar async function directly nahi likh sakte, kyunki useEffect ko synchronous function chahiye. Toh andar ek async function banao aur use call karo!

Data Fetching Demo

// โŒ WRONG: useEffect can't be async directly useEffect(async () => { const data = await fetch('/api/data'); }, []); // Error! // โœ… CORRECT: Create async function inside useEffect(() => { const fetchData = async () => { try { const response = await fetch('/api/data'); const data = await response.json(); setData(data); } catch (error) { console.error('Error:', error); } }; fetchData(); // Call the async function }, []); // Empty array = fetch only once // โœ… BETTER: With cleanup for race conditions useEffect(() => { let cancelled = false; // Flag for cleanup const fetchData = async () => { const response = await fetch('/api/data'); const data = await response.json(); if (!cancelled) { // Only update if not cancelled setData(data); } }; fetchData(); return () => { cancelled = true; // Cleanup: mark as cancelled }; }, []);

โšก useEffect vs useLayoutEffect

Difference kya hai? Dono almost same hain, bas timing different hai:
  • useEffect: Browser ke paint hone ke baad chalta hai (asynchronous)
  • useLayoutEffect: Browser ke paint hone se pehle chalta hai (synchronous)
Feature useEffect useLayoutEffect
Timing After browser paint Before browser paint
Blocking Non-blocking (async) Blocking (sync)
Use Case API calls, subscriptions, most side effects DOM measurements, preventing visual flicker
Performance Better (doesn't block paint) Can slow down if heavy operations
When to Use 99% of the time (default choice) Only when you need to read/write DOM before paint
// useEffect - Runs AFTER paint (user sees old value briefly) function EffectExample() { const [width, setWidth] = useState(0); const divRef = useRef(); useEffect(() => { setWidth(divRef.current.offsetWidth); }, []); return <div ref={divRef}>Width: {width}</div>; // User might see "Width: 0" briefly, then correct width } // useLayoutEffect - Runs BEFORE paint (no flicker) function LayoutEffectExample() { const [width, setWidth] = useState(0); const divRef = useRef(); useLayoutEffect(() => { setWidth(divRef.current.offsetWidth); }, []); return <div ref={divRef}>Width: {width}</div>; // User sees correct width immediately, no flicker }
๐Ÿ’ก Rule of Thumb: Hamesha useEffect use karo. Sirf tab useLayoutEffect use karo jab:
  • DOM measurements leni ho (scrollHeight, offsetWidth, etc.)
  • Visual flicker prevent karni ho
  • DOM ko paint se pehle modify karna ho

๐Ÿ’‰ useInsertionEffect

Ye kya hai? Ye React 18 mein aaya hai aur ye sabse pehle chalta hai - DOM mutations se bhi pehle! Lekin ye sirf CSS-in-JS libraries ke liye hai (jaise styled-components, emotion). Normal developers ko iska use nahi karna chahiye!
Technical Definition: useInsertionEffect fires synchronously before all DOM mutations, primarily designed for CSS-in-JS libraries to inject styles before layout effects read them.
Hook Execution Order Use Case
useInsertionEffect 1๏ธโƒฃ First (before DOM mutations) CSS-in-JS libraries only
useLayoutEffect 2๏ธโƒฃ Second (before browser paint) DOM measurements, preventing flicker
useEffect 3๏ธโƒฃ Third (after browser paint) Most side effects (API calls, subscriptions)
// useInsertionEffect - CSS-in-JS libraries ke liye function useCSS(rule) { useInsertionEffect(() => { // Inject CSS before any layout effects run const style = document.createElement('style'); style.textContent = rule; document.head.appendChild(style); return () => { document.head.removeChild(style); }; }, [rule]); } // โš ๏ธ Normal developers: DON'T use this! // Use useEffect or useLayoutEffect instead
โš ๏ธ Warning: useInsertionEffect ko normal development mein use mat karo! Ye sirf library authors ke liye hai. Agar tumhe DOM manipulation chahiye, use useLayoutEffect. Agar normal side effects chahiye, use useEffect.

๐ŸŽฏ Interview Questions

Apni understanding test karo! Click karke answers dekho.

Easy

Q1: What are the three main phases of a React component lifecycle?

Answer: The three main phases are:
  1. Mounting: Jab component DOM mein add hota hai (birth)
  2. Updating: Jab component re-render hota hai due to state/props changes (growth)
  3. Unmounting: Jab component DOM se remove hota hai (death)

Hinglish: Component ki life teen stages mein hoti hai - paida hona (mounting), bada hona (updating), aur marna (unmounting).

Easy

Q2: What is the purpose of the cleanup function in useEffect?

Answer: The cleanup function is used to:
  • Cancel subscriptions
  • Clear timers (setTimeout, setInterval)
  • Remove event listeners
  • Abort API requests
  • Prevent memory leaks

Hinglish: Cleanup function ka kaam hai safai karna - timers band karna, subscriptions cancel karna, taaki memory leak na ho. Ye component unmount hone se pehle ya next effect run hone se pehle chalta hai.

useEffect(() => {
  const timer = setInterval(() => console.log('tick'), 1000);
  
  return () => clearInterval(timer); // Cleanup!
}, []);
                        
Medium

Q3: Why were componentWillMount, componentWillReceiveProps, and componentWillUpdate marked as UNSAFE in React 16+?

Answer: These methods were marked UNSAFE because of React's new Fiber architecture:
  • Fiber enables asynchronous rendering - React can pause, abort, or restart rendering
  • These methods can be called multiple times before a single render completes
  • Side effects in these methods (API calls, subscriptions) can execute multiple times, causing bugs
  • They're not "pure" - they can have side effects that don't work well with interruption

Hinglish: React 16 mein Fiber architecture aaya jo rendering ko chhote pieces mein tod sakta hai. Purane "Will" methods beech mein multiple times call ho sakte the, jisse side effects duplicate ho jate the. Isliye inhe UNSAFE mark kar diya aur naye safe alternatives diye gaye.

Medium

Q4: What's the difference between useEffect with an empty dependency array [] and no dependency array at all?

Answer:
Scenario When Effect Runs
useEffect(() => {...}) After EVERY render (dangerous!)
useEffect(() => {...}, []) Only ONCE on mount (like componentDidMount)

Hinglish: Bina dependency array ke effect har render pe chalta hai (bahut dangerous!). Empty array [] ke saath sirf pehli baar mount pe chalta hai. Hamesha dependency array use karo!

Medium

Q5: How do you prevent infinite loops when using useEffect?

Answer: Infinite loops ko prevent karne ke liye:
  1. Always use dependency array - bina array ke har render pe chalega
  2. Add conditions - state update se pehle condition check karo
  3. Use primitive values in dependencies - objects/arrays har render pe naye bante hain
  4. Use useCallback/useMemo - functions aur objects ko memoize karo
  5. Don't update dependency inside effect - jo dependency hai use effect ke andar update mat karo (bina condition ke)
// โŒ Infinite loop
useEffect(() => {
  setCount(count + 1);
}, [count]);

// โœ… Fixed with condition
useEffect(() => {
  if (count < 10) {
    setCount(count + 1);
  }
}, [count]);

// โœ… Better: use functional update
useEffect(() => {
  setCount(prev => prev < 10 ? prev + 1 : prev);
}, []); // No dependency on count!
                        
Hard

Q6: When should you use useLayoutEffect instead of useEffect? Provide a real-world example.

Answer: Use useLayoutEffect when you need to:
  • Read DOM measurements (scrollHeight, offsetWidth, getBoundingClientRect)
  • Prevent visual flicker by updating DOM before paint
  • Synchronously re-render based on DOM measurements

Real-world example: Tooltip positioning

function Tooltip({ targetRef, children }) {
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const tooltipRef = useRef();

  useLayoutEffect(() => {
    // Measure target element
    const targetRect = targetRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();
    
    // Calculate position to avoid overflow
    const top = targetRect.bottom + 10;
    const left = targetRect.left - (tooltipRect.width / 2);
    
    setPosition({ top, left });
  }, [targetRef]);

  return (
    <div ref={tooltipRef} style={{ position: 'absolute', ...position }}>
      {children}
    </div>
  );
}
// useLayoutEffect ensures tooltip appears at correct position
// immediately, without flickering at wrong position first
                        

Hinglish: useLayoutEffect tab use karo jab DOM ko measure karna ho aur uske basis pe turant update karna ho, taaki user ko flicker na dikhe. Tooltip, dropdown positioning, scroll animations - yahan use hota hai.

Hard

Q7: Explain the race condition problem when fetching data in useEffect and how to solve it.

Answer: Race condition tab hota hai jab:
  • User quickly changes input (e.g., search query)
  • Multiple API requests fire in sequence
  • Responses arrive in different order than requests
  • Older response arrives last and overwrites newer data

Solution: Use cleanup function with cancellation flag

function SearchResults({ query }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    let cancelled = false; // Cancellation flag

    async function fetchResults() {
      const response = await fetch(`/api/search?q=${query}`);
      const data = await response.json();
      
      // Only update if this effect hasn't been cleaned up
      if (!cancelled) {
        setResults(data);
      }
    }

    fetchResults();

    // Cleanup: mark this request as cancelled
    return () => {
      cancelled = true;
    };
  }, [query]);

  return <div>{results.map(...)}</div>;
}

// Better: Use AbortController (modern approach)
useEffect(() => {
  const controller = new AbortController();

  fetch(`/api/search?q=${query}`, { signal: controller.signal })
    .then(res => res.json())
    .then(data => setResults(data))
    .catch(err => {
      if (err.name !== 'AbortError') {
        console.error(err);
      }
    });

  return () => controller.abort(); // Cancel request
}, [query]);
                        

Hinglish: Race condition ka matlab hai ki purani request ka response baad mein aa jaye aur nayi data ko overwrite kar de. Solution: cleanup function mein ek flag set karo ya AbortController use karke request cancel karo.