🪝 Advanced React Hooks

Master Complex State Management & Side Effects

Deep Dive into useReducer, useRef, useImperativeHandle & Custom Hooks

📚 Advanced Hooks Kya Hain?

Advanced Hooks React ke powerful features hain jo complex state management, DOM manipulation, aur reusable logic create karne mein help karte hain. Basic hooks (useState, useEffect) ke baad, yeh hooks aapko production-level applications banane ki capability dete hain.

Jab aapka application bada ho jata hai, tab simple useState kaafi nahi hota. Tab aapko useReducer chahiye complex state logic ke liye, useRef chahiye DOM elements ko directly access karne ke liye, aur custom hooks chahiye reusable logic banane ke liye.

🎯 Kyun Seekhna Zaroori Hai?

  • Complex State Management: Multiple related states ko efficiently manage karo
  • Performance Optimization: Unnecessary re-renders avoid karo
  • DOM Access: Direct DOM manipulation jab zaroori ho
  • Reusable Logic: Custom hooks se code duplication reduce karo
  • Production Ready: Real-world applications mein use hone wale patterns
useState
→
useReducer
→
useRef
→
Custom Hooks
💡 Learning Path: Pehle basic hooks (useState, useEffect) master karo, phir advanced hooks seekho. Har hook ka apna specific use case hai - sab jagah har hook use karne ki zaroorat nahi hai!

1️⃣ useReducer - Complex State Management

useReducer ek hook hai jo complex state logic ko manage karne ke liye use hota hai. Jab aapke paas multiple sub-values hain ya next state previous state pe depend karta hai, tab useReducer useState se better hai. Yeh Redux pattern follow karta hai - actions dispatch karo aur reducer function state update karta hai.
// Basic useReducer Syntax const [state, dispatch] = useReducer(reducer, initialState); // Reducer Function - Pure function jo state aur action leta hai function reducer(state, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; case 'RESET': return { count: 0 }; default: return state; } } // Component mein use function Counter() { const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button> <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button> <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button> </div> ); } // Complex Example - Form State Management const initialFormState = { username: '', email: '', password: '', errors: {}, isSubmitting: false }; function formReducer(state, action) { switch (action.type) { case 'UPDATE_FIELD': return { ...state, [action.field]: action.value, errors: { ...state.errors, [action.field]: null } }; case 'SET_ERRORS': return { ...state, errors: action.errors }; case 'SUBMIT_START': return { ...state, isSubmitting: true }; case 'SUBMIT_SUCCESS': return initialFormState; case 'SUBMIT_ERROR': return { ...state, isSubmitting: false, errors: action.errors }; default: return state; } }
🎮 Interactive Demo: useReducer Counter
0
Current State:
{ count: 0 }
📋 Action Log:

❌ useState - Complex State

const [count, setCount] = useState(0); const [step, setStep] = useState(1); const [history, setHistory] = useState([]); // Multiple setState calls const increment = () => { setCount(count + step); setHistory([...history, count + step]); };

Multiple related states, hard to maintain!

✅ useReducer - Clean & Organized

const [state, dispatch] = useReducer( reducer, { count: 0, step: 1, history: [] } ); // Single dispatch const increment = () => { dispatch({ type: 'INCREMENT' }); };

Single source of truth, predictable updates!

⚠️ useReducer vs useState - Kab Kaunsa Use Karein:

useState use karo jab:
• Simple, independent state values
• State updates simple hain
• Ek ya do state variables

useReducer use karo jab:
• Complex state logic with multiple sub-values
• Next state depends on previous state
• State transitions need to be predictable
• Multiple related state updates
• Testing aur debugging easy banana hai

2️⃣ useRef - Mutable Values & DOM Access

useRef ek hook hai jo do main purposes ke liye use hota hai:

1. DOM Elements ko Access karna: Direct DOM manipulation ke liye (focus, scroll, animations)
2. Mutable Values Store karna: Values jo re-render trigger nahi karti (timers, previous values, etc.)

useRef ek object return karta hai jiska .current property mutable hai. Yeh value change hone pe component re-render nahi hota, jo useState se different hai.
// Basic useRef Syntax const refContainer = useRef(initialValue); // DOM Reference Example function TextInputWithFocusButton() { const inputRef = useRef(null); const focusInput = () => { // Direct DOM access inputRef.current.focus(); }; return ( <> <input ref={inputRef} type="text" /> <button onClick={focusInput}>Focus Input</button> </> ); } // Mutable Value Example - Previous State function Counter() { const [count, setCount] = useState(0); const prevCountRef = useRef(); useEffect(() => { prevCountRef.current = count; }); const prevCount = prevCountRef.current; return ( <div> <p>Current: {count}, Previous: {prevCount}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } // Timer Example - Storing Interval ID function Timer() { const [seconds, setSeconds] = useState(0); const intervalRef = useRef(); const startTimer = () => { intervalRef.current = setInterval(() => { setSeconds(s => s + 1); }, 1000); }; const stopTimer = () => { clearInterval(intervalRef.current); }; useEffect(() => { return () => clearInterval(intervalRef.current); }, []); return ( <div> <p>Seconds: {seconds}</p> <button onClick={startTimer}>Start</button> <button onClick={stopTimer}>Stop</button> </div> ); } // Scroll to Element Example function ScrollToSection() { const sectionRef = useRef(null); const scrollToSection = () => { sectionRef.current.scrollIntoView({ behavior: 'smooth' }); }; return ( <> <button onClick={scrollToSection}>Scroll Down</button> <div style={{ height: '1000px' }}>Content...</div> <div ref={sectionRef}>Target Section</div> </> ); }
🎮 Interactive Demo: useRef Examples
Click Me!
Box 1
Click Me!
Box 2
Click Count (without re-render):
Box 1: 0 clicks
Box 2: 0 clicks
💡 Notice: Click counts update hote hain lekin component re-render nahi hota! Yeh useRef ki power hai - mutable values without re-renders.
Feature useState useRef
Re-render ✅ Triggers re-render ❌ No re-render
Value Access Direct value .current property
Update setState function Direct assignment
Use Case UI state DOM refs, timers, previous values
Persistence Across re-renders Across re-renders
✅ useRef Best Practices:

• DOM manipulation ke liye use karo (focus, scroll, animations)
• Timers aur intervals store karne ke liye
• Previous values track karne ke liye
• Instance variables (class components ke this.variable jaisa)
• Third-party library instances store karne ke liye

❌ Avoid:
• UI state ke liye (use useState instead)
• Ref values ko render mein directly use karna
• Ref values pe conditional rendering

3️⃣ useImperativeHandle - Parent-Child Communication

useImperativeHandle ek advanced hook hai jo parent component ko child component ke internal methods expose karne deta hai. Yeh forwardRef ke saath use hota hai aur parent ko child ke specific functions call karne ki permission deta hai.

Normally React mein data flow top-down hota hai (props through), lekin kabhi-kabhi parent ko child ke methods directly call karne padte hain. Tab useImperativeHandle use hota hai.
// Basic useImperativeHandle Example import { forwardRef, useImperativeHandle, useRef } from 'react'; // Child Component const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); // Expose specific methods to parent useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, clear: () => { inputRef.current.value = ''; }, setValue: (value) => { inputRef.current.value = value; } })); return <input ref={inputRef} {...props} />; }); // Parent Component function Parent() { const inputRef = useRef(); const handleFocus = () => { inputRef.current.focus(); }; const handleClear = () => { inputRef.current.clear(); }; return ( <> <FancyInput ref={inputRef} /> <button onClick={handleFocus}>Focus</button> <button onClick={handleClear}>Clear</button> </> ); } // Advanced Example - Video Player Control const VideoPlayer = forwardRef((props, ref) => { const videoRef = useRef(); const [isPlaying, setIsPlaying] = useState(false); useImperativeHandle(ref, () => ({ play: () => { videoRef.current.play(); setIsPlaying(true); }, pause: () => { videoRef.current.pause(); setIsPlaying(false); }, seek: (time) => { videoRef.current.currentTime = time; }, getStatus: () => ({ isPlaying, currentTime: videoRef.current.currentTime, duration: videoRef.current.duration }) })); return <video ref={videoRef} src={props.src} />; }); // Modal Example const Modal = forwardRef((props, ref) => { const [isOpen, setIsOpen] = useState(false); useImperativeHandle(ref, () => ({ open: () => setIsOpen(true), close: () => setIsOpen(false), toggle: () => setIsOpen(prev => !prev) })); if (!isOpen) return null; return ( <div className="modal"> {props.children} </div> ); });
🎮 Interactive Demo: useImperativeHandle

Custom Input Component

Output will appear here...

Modal Control

⚠️ Important Notes:

• useImperativeHandle ko sparingly use karo - yeh React ke declarative nature ke against hai
• Sirf jab imperative actions zaroori ho tab use karo (focus, scroll, play/pause)
• Hamesha forwardRef ke saath use hota hai
• Parent-child coupling increase hota hai, so carefully use karo
• Better alternative: Controlled components with props and callbacks

❌ Without useImperativeHandle

Parent ko child ke internal state/methods access nahi hote. Har cheez props through pass karni padti hai, jo complex ho sakta hai.

✅ With useImperativeHandle

Parent directly child ke exposed methods call kar sakta hai. Clean API for imperative actions like focus, play, pause.

4️⃣ useLayoutEffect - Synchronous Side Effects

useLayoutEffect useEffect jaisa hi hai, lekin yeh synchronously fire hota hai DOM mutations ke baad aur browser ke paint karne se pehle. Iska matlab hai ki agar aapko DOM measurements leni hain ya visual changes karne hain before browser paints, toh useLayoutEffect use karo.
// useEffect vs useLayoutEffect // useEffect - Asynchronous, after paint useEffect(() => { // Runs AFTER browser has painted // User might see flicker if you update DOM here }, []); // useLayoutEffect - Synchronous, before paint useLayoutEffect(() => { // Runs BEFORE browser paints // Blocks visual updates until complete }, []); // Example: Measuring DOM Elements function MeasureElement() { const [height, setHeight] = useState(0); const elementRef = useRef(); useLayoutEffect(() => { // Measure before paint - no flicker const rect = elementRef.current.getBoundingClientRect(); setHeight(rect.height); }, []); return ( <div> <div ref={elementRef}>Content</div> <p>Height: {height}px</p> </div> ); } // Example: Tooltip Positioning function Tooltip({ targetRef, children }) { const [position, setPosition] = useState({ top: 0, left: 0 }); const tooltipRef = useRef(); useLayoutEffect(() => { const targetRect = targetRef.current.getBoundingClientRect(); const tooltipRect = tooltipRef.current.getBoundingClientRect(); // Calculate position before paint setPosition({ top: targetRect.bottom + 10, left: targetRect.left + (targetRect.width - tooltipRect.width) / 2 }); }, [targetRef]); return ( <div ref={tooltipRef} style={{ position: 'absolute', ...position }} > {children} </div> ); } // Example: Scroll Position Restoration function ScrollRestoration() { const scrollRef = useRef(); useLayoutEffect(() => { // Restore scroll position before paint const savedPosition = localStorage.getItem('scrollPosition'); if (savedPosition) { scrollRef.current.scrollTop = parseInt(savedPosition); } }, []); return <div ref={scrollRef}>Long content...</div>; }
Render
→
useLayoutEffect
→
Browser Paint
→
useEffect
Feature useEffect useLayoutEffect
Timing After paint (async) Before paint (sync)
Blocks Painting ❌ No ✅ Yes
Use Case Data fetching, subscriptions DOM measurements, animations
Performance Better (non-blocking) Can block if slow
Visual Flicker Possible Prevented
🚨 When to Use useLayoutEffect:

✅ Use karo jab:
• DOM measurements leni hain (getBoundingClientRect)
• Visual changes before paint (positioning, animations)
• Scroll position restoration
• Preventing visual flicker is critical

❌ Use mat karo jab:
• Data fetching
• Event listeners
• Subscriptions
• Most side effects (use useEffect instead)

⚠️ Warning: useLayoutEffect blocks browser painting, so heavy operations can make UI feel sluggish. Use sparingly!

5️⃣ Custom Hooks - Reusable Logic

Custom Hooks aapke khud ke hooks hain jo reusable logic encapsulate karte hain. Yeh functions hain jo React hooks use karte hain aur "use" prefix se start hote hain. Custom hooks se aap complex logic ko extract kar sakte ho aur multiple components mein reuse kar sakte ho.
// Custom Hook Example 1: useLocalStorage function useLocalStorage(key, initialValue) { // State to store value const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.log(error); return initialValue; } }); // Return wrapped version of useState's setter const setValue = (value) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.log(error); } }; return [storedValue, setValue]; } // Usage function App() { const [name, setName] = useLocalStorage('name', 'Guest'); return <input value={name} onChange={(e) => setName(e.target.value)} />; } // Custom Hook Example 2: useFetch function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; setLoading(true); fetch(url) .then(response => response.json()) .then(data => { if (!cancelled) { setData(data); setLoading(false); } }) .catch(error => { if (!cancelled) { setError(error); setLoading(false); } }); return () => { cancelled = true; }; }, [url]); return { data, loading, error }; } // Custom Hook Example 3: useDebounce function useDebounce(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } // Custom Hook Example 4: useWindowSize function useWindowSize() { const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handleResize = () => { setWindowSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return windowSize; } // Custom Hook Example 5: useToggle function useToggle(initialValue = false) { const [value, setValue] = useState(initialValue); const toggle = useCallback(() => { setValue(v => !v); }, []); return [value, toggle]; }
🎮 Interactive Demo: Custom Hooks

useLocalStorage Hook

Stored Value: None

useDebounce Hook

Immediate Value: -
Debounced Value: -

useToggle Hook

OFF

useWindowSize Hook

Width: -px
Height: -px

Resize your browser window to see live updates!

✅ Custom Hooks Best Practices:

• Always start with "use" prefix (useMyHook)
• Extract reusable logic from components
• Keep hooks focused on single responsibility
• Return values in array or object format
• Document parameters and return values
• Handle cleanup in useEffect
• Make hooks composable (hooks can use other hooks)
• Test hooks independently

Popular Custom Hooks Examples

Hook Name Purpose Returns
useLocalStorage Sync state with localStorage [value, setValue]
useFetch Data fetching with loading/error states { data, loading, error }
useDebounce Delay value updates debouncedValue
useWindowSize Track window dimensions { width, height }
useToggle Boolean state toggle [value, toggle]
useOnClickOutside Detect clicks outside element ref
useInterval Declarative setInterval void
usePrevious Track previous value previousValue

🎯 Interview Questions

Master these questions to ace your Advanced Hooks interviews!

Easy

Q1: useReducer kya hai aur useState se kab better hai? Example ke saath explain karo.

Answer:

useReducer kya hai:
useReducer ek hook hai jo complex state logic manage karne ke liye use hota hai. Yeh Redux pattern follow karta hai - actions dispatch karo aur reducer function state update karta hai.

Syntax:
const [state, dispatch] = useReducer(reducer, initialState);

useState se kab better hai:

useState use karo jab:
• Simple, independent state values
• Ek ya do state variables
• State updates simple hain

useReducer use karo jab:
• Complex state logic with multiple sub-values
• Next state depends on previous state
• Multiple related state updates
• State transitions predictable banana hai

Example:
// useState - Multiple states
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const [history, setHistory] = useState([]);

// useReducer - Single state object
const [state, dispatch] = useReducer(reducer, {
  count: 0,
  step: 1,
  history: []
});

// Reducer function
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + state.step,
        history: [...state.history, state.count + state.step]
      };
    case 'SET_STEP':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

Benefits:
• Single source of truth
• Predictable state updates
• Easy to test (pure functions)
• Better for complex state logic
• Easier to debug with action logs
Easy

Q2: useRef kya hai aur useState se kya difference hai? Kab useRef use karna chahiye?

Answer:

useRef kya hai:
useRef ek hook hai jo mutable value store karta hai jo re-render trigger nahi karta. Yeh ek object return karta hai jiska .current property mutable hai.

Two Main Uses:
1. DOM References: DOM elements ko directly access karna
2. Mutable Values: Values jo re-render nahi karti (timers, previous values)

useState vs useRef:
Feature useState useRef
Re-render ✅ Triggers ❌ No re-render
Value Access Direct .current property
Update setState function Direct assignment
Use Case UI state DOM refs, timers

Kab useRef use karein:

✅ Use karo jab:
• DOM elements ko access karna hai (focus, scroll)
• Timers/intervals store karne hain
• Previous values track karne hain
• Instance variables chahiye (class component ke this.variable jaisa)
• Third-party library instances store karne hain

Examples:
// DOM Reference
const inputRef = useRef(null);
const focusInput = () => inputRef.current.focus();

// Mutable Value (no re-render)
const countRef = useRef(0);
countRef.current++; // No re-render!

// Timer
const intervalRef = useRef();
intervalRef.current = setInterval(() => {...}, 1000);

// Previous Value
const prevValueRef = useRef();
useEffect(() => {
  prevValueRef.current = value;
});
Medium

Q3: useImperativeHandle kya hai? Kab aur kyun use karte hain? forwardRef ke saath kaise kaam karta hai?

Answer:

useImperativeHandle kya hai:
useImperativeHandle ek hook hai jo parent component ko child component ke specific methods expose karne deta hai. Yeh forwardRef ke saath use hota hai aur parent ko child ke internal functions call karne ki permission deta hai.

Kab use karte hain:
• Jab parent ko child ke imperative methods call karne hain
• DOM operations (focus, scroll, play/pause)
• Child component ka internal API expose karna hai
• Controlled components with imperative actions

forwardRef ke saath kaise kaam karta hai:
// Step 1: forwardRef se child component wrap karo
const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  
  // Step 2: useImperativeHandle se methods expose karo
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    },
    setValue: (value) => {
      inputRef.current.value = value;
    }
  }));
  
  return <input ref={inputRef} {...props} />;
});

// Step 3: Parent mein use karo
function Parent() {
  const inputRef = useRef();
  
  const handleFocus = () => {
    inputRef.current.focus(); // Call child method
  };
  
  return (
    <>
      <FancyInput ref={inputRef} />
      <button onClick={handleFocus}>Focus</button>
    </>
  );
}

Real-World Example - Video Player:
const VideoPlayer = forwardRef((props, ref) => {
  const videoRef = useRef();
  
  useImperativeHandle(ref, () => ({
    play: () => videoRef.current.play(),
    pause: () => videoRef.current.pause(),
    seek: (time) => videoRef.current.currentTime = time,
    getStatus: () => ({
      currentTime: videoRef.current.currentTime,
      duration: videoRef.current.duration
    })
  }));
  
  return <video ref={videoRef} src={props.src} />;
});

⚠️ Important Notes:
• Sparingly use karo - React ke declarative nature ke against hai
• Sirf imperative actions ke liye (focus, play, pause)
• Parent-child coupling increase hota hai
• Better alternative: Controlled components with props
Medium

Q4: useLayoutEffect aur useEffect mein kya difference hai? Kab useLayoutEffect use karna chahiye?

Answer:

Main Difference:
• useEffect: Asynchronously runs AFTER browser paint
• useLayoutEffect: Synchronously runs BEFORE browser paint

Execution Order:
1. Component renders
2. useLayoutEffect runs (blocks painting)
3. Browser paints screen
4. useEffect runs (non-blocking)

Detailed Comparison:
Feature useEffect useLayoutEffect
Timing After paint Before paint
Blocks Paint ❌ No ✅ Yes
Execution Async Sync
Performance Better Can block
Visual Flicker Possible Prevented

Kab useLayoutEffect use karein:

✅ Use karo jab:
• DOM measurements leni hain (getBoundingClientRect)
• Visual changes before paint (positioning, animations)
• Scroll position restoration
• Tooltip/popover positioning
• Preventing visual flicker is critical

❌ Use mat karo jab:
• Data fetching
• Event listeners
• Subscriptions
• Most side effects (use useEffect)

Examples:
// ❌ useEffect - Might see flicker
useEffect(() => {
  const rect = elementRef.current.getBoundingClientRect();
  setHeight(rect.height); // User might see jump
}, []);

// ✅ useLayoutEffect - No flicker
useLayoutEffect(() => {
  const rect = elementRef.current.getBoundingClientRect();
  setHeight(rect.height); // Smooth, no jump
}, []);

// Tooltip positioning
useLayoutEffect(() => {
  const targetRect = targetRef.current.getBoundingClientRect();
  const tooltipRect = tooltipRef.current.getBoundingClientRect();
  
  // Calculate position before paint
  setPosition({
    top: targetRect.bottom + 10,
    left: targetRect.left
  });
}, []);

⚠️ Warning:
useLayoutEffect blocks browser painting, so heavy operations can make UI sluggish. Use sparingly and only when necessary!
Hard

Q5: Custom hooks kaise banate hain? Best practices kya hain? Real-world example ke saath explain karo.

Answer:

Custom Hooks kaise banate hain:
Custom hooks simple JavaScript functions hain jo React hooks use karte hain aur "use" prefix se start hote hain. Yeh reusable logic encapsulate karte hain.

Basic Structure:
function useCustomHook(params) {
  // 1. Use React hooks
  const [state, setState] = useState(initialValue);
  
  // 2. Add logic
  useEffect(() => {
    // Side effects
  }, [dependencies]);
  
  // 3. Return values
  return [state, setState];
}

Real-World Example - useFetch:
function useFetch(url, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url, options);
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const json = await response.json();
        
        if (!cancelled) {
          setData(json);
          setError(null);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
          setData(null);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };
    
    fetchData();
    
    return () => {
      cancelled = true;
    };
  }, [url, JSON.stringify(options)]);
  
  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return <div>{data.name}</div>;
}

Advanced Example - useForm:
function useForm(initialValues, validate) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
    
    // Clear error when user types
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: null }));
    }
  };
  
  const handleBlur = (e) => {
    const { name } = e.target;
    setTouched(prev => ({ ...prev, [name]: true }));
    
    // Validate on blur
    if (validate) {
      const fieldError = validate(name, values[name]);
      if (fieldError) {
        setErrors(prev => ({ ...prev, [name]: fieldError }));
      }
    }
  };
  
  const handleSubmit = async (onSubmit) => {
    return async (e) => {
      e.preventDefault();
      setIsSubmitting(true);
      
      // Validate all fields
      if (validate) {
        const validationErrors = {};
        Object.keys(values).forEach(key => {
          const error = validate(key, values[key]);
          if (error) validationErrors[key] = error;
        });
        
        if (Object.keys(validationErrors).length > 0) {
          setErrors(validationErrors);
          setIsSubmitting(false);
          return;
        }
      }
      
      try {
        await onSubmit(values);
      } catch (error) {
        setErrors({ submit: error.message });
      } finally {
        setIsSubmitting(false);
      }
    };
  };
  
  const reset = () => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
    setIsSubmitting(false);
  };
  
  return {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit,
    reset
  };
}

Best Practices:

1. Naming:
• Always start with "use" prefix
• Descriptive names (useAuth, useFetch, useForm)

2. Return Values:
• Array for 2 values: [value, setValue]
• Object for multiple values: { data, loading, error }

3. Dependencies:
• Properly manage useEffect dependencies
• Cleanup functions for subscriptions/timers

4. Composition:
• Hooks can use other hooks
• Keep hooks focused (single responsibility)

5. Error Handling:
• Handle errors gracefully
• Provide error states

6. Testing:
• Test hooks independently
• Use @testing-library/react-hooks

Common Custom Hooks:
• useLocalStorage - Sync with localStorage
• useDebounce - Delay value updates
• useWindowSize - Track window dimensions
• useOnClickOutside - Detect outside clicks
• useInterval - Declarative setInterval
• usePrevious - Track previous value
• useToggle - Boolean state toggle
• useAsync - Async operation handling
Hard

Q6: useReducer aur Redux mein kya difference hai? Production app mein kab kaunsa use karein?

Answer:

useReducer vs Redux:

Feature useReducer Redux
Scope Component level Application level
Setup Built-in, no installation External library
DevTools ❌ No ✅ Redux DevTools
Middleware ❌ No ✅ Yes (thunk, saga)
Time Travel ❌ No ✅ Yes
Bundle Size 0 KB (built-in) ~10 KB
Learning Curve Easy Moderate

useReducer Example:
// Component-level state
function TodoList() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  
  // State is local to this component
  return (
    <div>
      {state.todos.map(todo => 
        <Todo key={todo.id} {...todo} />
      )}
    </div>
  );
}

Redux Example:
// Global state accessible anywhere
import { useSelector, useDispatch } from 'react-redux';

function TodoList() {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();
  
  // State is global, accessible from any component
  return (
    <div>
      {todos.map(todo => 
        <Todo key={todo.id} {...todo} />
      )}
    </div>
  );
}

Kab kaunsa use karein:

useReducer use karo jab:
✅ Component-level complex state
✅ State sirf ek component tree mein chahiye
✅ Small to medium applications
✅ No need for middleware
✅ Want to keep bundle size small
✅ Simple state management

Redux use karo jab:
✅ Global state across entire app
✅ Multiple components need same state
✅ Large, complex applications
✅ Need middleware (async actions, logging)
✅ Want Redux DevTools for debugging
✅ Time-travel debugging needed
✅ Team familiar with Redux patterns

Hybrid Approach:
// Use both together!
// Redux for global state (user, auth, theme)
// useReducer for local complex state (forms, modals)

function ComplexForm() {
  // Local form state with useReducer
  const [formState, dispatch] = useReducer(formReducer, initialState);
  
  // Global user state from Redux
  const user = useSelector(state => state.user);
  
  return <form>...</form>;
}

Modern Alternative - Context + useReducer:
// Create global state without Redux
const StateContext = createContext();
const DispatchContext = createContext();

function StateProvider({ children }) {
  const [state, dispatch] = useReducer(rootReducer, initialState);
  
  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}

// Custom hooks for easy access
function useState() {
  return useContext(StateContext);
}

function useDispatch() {
  return useContext(DispatchContext);
}

Decision Tree:
1. Need global state? → Yes → Redux or Context+useReducer
2. Need middleware? → Yes → Redux
3. Need DevTools? → Yes → Redux
4. Component-level only? → Yes → useReducer
5. Simple state? → Yes → useState

Recommendation:
• Small apps: useState + useReducer
• Medium apps: Context + useReducer
• Large apps: Redux or Zustand
• Enterprise: Redux with Redux Toolkit
Hard

Q7: Production-ready custom hook kaise banayein? Error handling, cleanup, aur optimization ke saath complete example do.

Answer:

Production-Ready Custom Hook - useAsync:
import { useState, useEffect, useCallback, useRef } from 'react';

function useAsync(asyncFunction, immediate = true) {
  // State management
  const [status, setStatus] = useState('idle');
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  
  // Refs for cleanup and preventing memory leaks
  const mountedRef = useRef(true);
  const pendingPromiseRef = useRef(null);
  
  // Execute async function
  const execute = useCallback((...params) => {
    setStatus('pending');
    setData(null);
    setError(null);
    
    // Store promise for potential cancellation
    const promise = asyncFunction(...params);
    pendingPromiseRef.current = promise;
    
    return promise
      .then(response => {
        // Only update if component is still mounted
        if (mountedRef.current) {
          setData(response);
          setStatus('success');
        }
        return response;
      })
      .catch(error => {
        if (mountedRef.current) {
          setError(error);
          setStatus('error');
        }
        throw error;
      })
      .finally(() => {
        if (mountedRef.current) {
          pendingPromiseRef.current = null;
        }
      });
  }, [asyncFunction]);
  
  // Reset function
  const reset = useCallback(() => {
    if (mountedRef.current) {
      setStatus('idle');
      setData(null);
      setError(null);
    }
  }, []);
  
  // Execute immediately if requested
  useEffect(() => {
    if (immediate) {
      execute();
    }
  }, [execute, immediate]);
  
  // Cleanup on unmount
  useEffect(() => {
    return () => {
      mountedRef.current = false;
    };
  }, []);
  
  // Return values and functions
  return {
    execute,
    reset,
    status,
    data,
    error,
    isIdle: status === 'idle',
    isPending: status === 'pending',
    isSuccess: status === 'success',
    isError: status === 'error'
  };
}

// Usage Example
function UserProfile({ userId }) {
  const fetchUser = useCallback(
    () => fetch(`/api/users/${userId}`).then(r => r.json()),
    [userId]
  );
  
  const {
    data: user,
    error,
    isPending,
    isError,
    execute: refetch
  } = useAsync(fetchUser);
  
  if (isPending) return <Spinner />;
  if (isError) return <Error error={error} onRetry={refetch} />;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <button onClick={refetch}>Refresh</button>
    </div>
  );
}

Advanced Example - useWebSocket:
function useWebSocket(url, options = {}) {
  const [data, setData] = useState(null);
  const [status, setStatus] = useState('connecting');
  const [error, setError] = useState(null);
  
  const wsRef = useRef(null);
  const reconnectTimeoutRef = useRef(null);
  const reconnectAttemptsRef = useRef(0);
  
  const {
    reconnect = true,
    reconnectInterval = 3000,
    maxReconnectAttempts = 5,
    onOpen,
    onClose,
    onError,
    onMessage
  } = options;
  
  // Send message function
  const sendMessage = useCallback((message) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(
        typeof message === 'string' ? message : JSON.stringify(message)
      );
      return true;
    }
    return false;
  }, []);
  
  // Connect function
  const connect = useCallback(() => {
    try {
      setStatus('connecting');
      const ws = new WebSocket(url);
      
      ws.onopen = (event) => {
        setStatus('connected');
        setError(null);
        reconnectAttemptsRef.current = 0;
        onOpen?.(event);
      };
      
      ws.onmessage = (event) => {
        try {
          const parsedData = JSON.parse(event.data);
          setData(parsedData);
          onMessage?.(parsedData);
        } catch {
          setData(event.data);
          onMessage?.(event.data);
        }
      };
      
      ws.onerror = (event) => {
        setError(new Error('WebSocket error'));
        setStatus('error');
        onError?.(event);
      };
      
      ws.onclose = (event) => {
        setStatus('disconnected');
        onClose?.(event);
        
        // Attempt reconnection
        if (
          reconnect &&
          reconnectAttemptsRef.current < maxReconnectAttempts
        ) {
          reconnectAttemptsRef.current++;
          reconnectTimeoutRef.current = setTimeout(() => {
            connect();
          }, reconnectInterval);
        }
      };
      
      wsRef.current = ws;
    } catch (err) {
      setError(err);
      setStatus('error');
    }
  }, [url, reconnect, reconnectInterval, maxReconnectAttempts, onOpen, onClose, onError, onMessage]);
  
  // Disconnect function
  const disconnect = useCallback(() => {
    if (reconnectTimeoutRef.current) {
      clearTimeout(reconnectTimeoutRef.current);
    }
    if (wsRef.current) {
      wsRef.current.close();
      wsRef.current = null;
    }
  }, []);
  
  // Connect on mount
  useEffect(() => {
    connect();
    return () => {
      disconnect();
    };
  }, [connect, disconnect]);
  
  return {
    data,
    status,
    error,
    sendMessage,
    connect,
    disconnect,
    isConnecting: status === 'connecting',
    isConnected: status === 'connected',
    isDisconnected: status === 'disconnected',
    isError: status === 'error'
  };
}

Production Best Practices:

1. Error Handling:
• Try-catch blocks for all async operations
• Provide error states and messages
• Handle edge cases (network errors, timeouts)

2. Cleanup:
• Always cleanup in useEffect return
• Cancel pending requests on unmount
• Clear timers and intervals
• Close connections (WebSocket, SSE)

3. Memory Leaks Prevention:
• Use mounted ref to prevent setState on unmounted component
• Cancel async operations on unmount
• Remove event listeners

4. Optimization:
• Use useCallback for functions
• Use useMemo for expensive computations
• Proper dependency arrays
• Debounce/throttle when needed

5. TypeScript Support:
function useAsync<T>(
  asyncFunction: (...args: any[]) => Promise<T>,
  immediate = true
): {
  execute: (...args: any[]) => Promise<T>;
  reset: () => void;
  status: 'idle' | 'pending' | 'success' | 'error';
  data: T | null;
  error: Error | null;
  isIdle: boolean;
  isPending: boolean;
  isSuccess: boolean;
  isError: boolean;
} {
  // Implementation...
}

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

describe('useAsync', () => {
  it('should handle successful async operation', async () => {
    const asyncFn = jest.fn().mockResolvedValue('data');
    const { result, waitForNextUpdate } = renderHook(() => 
      useAsync(asyncFn)
    );
    
    expect(result.current.isPending).toBe(true);
    
    await waitForNextUpdate();
    
    expect(result.current.isSuccess).toBe(true);
    expect(result.current.data).toBe('data');
  });
  
  it('should handle errors', async () => {
    const error = new Error('Failed');
    const asyncFn = jest.fn().mockRejectedValue(error);
    const { result, waitForNextUpdate } = renderHook(() => 
      useAsync(asyncFn)
    );
    
    await waitForNextUpdate();
    
    expect(result.current.isError).toBe(true);
    expect(result.current.error).toBe(error);
  });
});

7. Documentation:
• JSDoc comments
• Usage examples
• Parameter descriptions
• Return value documentation

Checklist:
☑️ Error handling implemented
☑️ Cleanup functions added
☑️ Memory leak prevention
☑️ TypeScript types defined
☑️ Tests written
☑️ Documentation added
☑️ Performance optimized
☑️ Edge cases handled