Skip to main content

πŸ“˜ Custom Hooks in React β€” Complete Theory Guide


1. πŸ“Œ Introduction

πŸ”Ή What are Custom Hooks?

Custom Hooks are JavaScript functions that:
  • Start with the prefix use
  • Allow you to reuse stateful logic across multiple components
They are built using existing React hooks like:
  • useState
  • useEffect
  • useRef
  • etc.
πŸ‘‰ In simple terms: Custom hooks = reusable logic extracted from components

πŸ”Ή Why are Custom Hooks Important?

Without custom hooks:
  • Logic gets duplicated across components
  • Components become large and harder to maintain
With custom hooks:
  • Logic becomes modular and reusable
  • Components become clean and focused on UI
πŸ’‘ Think of them as:
β€œUtility functions for React logic”

πŸ”Ή When and Why Do We Use Them?

Use custom hooks when:
  • πŸ” You repeat logic across multiple components
  • 🧠 A component has too much logic mixed with UI
  • 🧩 You want to separate concerns (logic vs presentation)
  • βš™οΈ You want to abstract complex behavior (API calls, forms, timers)

Example Use Cases:

  • Fetching data (useFetch)
  • Form handling (useForm)
  • Window resize tracking (useWindowSize)
  • Authentication (useAuth)
  • Debouncing input (useDebounce)

2. βš™οΈ Concepts / Internal Workings

πŸ”Ή 1. Hooks are Just Functions (But with Rules)

Custom hooks:
  • Are plain JavaScript functions
  • Can call other hooks
  • Must follow Rules of Hooks

πŸ”Ή 2. Rules of Hooks (Critical)

  1. Only call hooks:
    • At the top level (not inside loops/conditions)
  2. Only call hooks:
    • Inside React functions (components or custom hooks)
❌ Wrong:
if (condition) {
  useEffect(() => {});
}
βœ… Correct:
useEffect(() => {
  if (condition) {
    // logic
  }
}, [condition]);

πŸ”Ή 3. How Custom Hooks Work Internally

React internally tracks hooks using a call order system. πŸ‘‰ Important idea:
  • React does NOT track hooks by name
  • It tracks them by order of execution
Example:
useState();
useEffect();
React internally:
Hook #1 β†’ useState
Hook #2 β†’ useEffect
So:
  • If order changes β†’ React breaks ❌
  • That’s why hooks must always run in the same order

πŸ”Ή 4. Each Hook Has Its Own State

Every time you use a custom hook:
  • It creates independent state instances
const count1 = useCounter();
const count2 = useCounter();
πŸ‘‰ These do NOT share state unless explicitly designed to

πŸ”Ή 5. Relationship with Other React Features

FeatureRelationship
useStateUsed inside custom hooks for state
useEffectUsed for side effects
useContextShare global data
useReducerManage complex state
useRefStore persistent mutable values
ComponentsConsume custom hooks
πŸ‘‰ Custom hooks are a composition layer over these primitives

3. πŸ§ͺ Syntax & Examples


πŸ”Ή Basic Syntax

function useCustomHook() {
  // use other hooks
  return something;
}

πŸ”Ή Example 1: Counter Hook

import { useState } from "react";

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
}

Usage:

function App() {
  const { count, increment } = useCounter(5);

  return <button onClick={increment}>{count}</button>;
}

πŸ”Ή Example 2: Fetch Data Hook

import { useState, useEffect } from "react";

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;

    setLoading(true);

    fetch(url)
      .then(res => res.json())
      .then(data => {
        if (isMounted) {
          setData(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (isMounted) {
          setError(err);
          setLoading(false);
        }
      });

    return () => {
      isMounted = false;
    };
  }, [url]);

  return { data, loading, error };
}

πŸ”Ή Example 3: Window Size Hook

import { useState, useEffect } from "react";

function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    function handleResize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }

    window.addEventListener("resize", handleResize);

    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return size;
}

πŸ”Ή Example 4: Debounce Hook

import { useState, useEffect } from "react";

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const id = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(id);
  }, [value, delay]);

  return debouncedValue;
}

πŸ”Ή Example 5: Sharing Logic Across Components

function SearchComponent() {
  const debouncedQuery = useDebounce(query, 500);
}

function FilterComponent() {
  const debouncedFilter = useDebounce(filter, 300);
}
πŸ‘‰ Same logic, different components β†’ clean reuse

4. ⚠️ Edge Cases / Common Mistakes


πŸ”Ή 1. Breaking Hook Order

❌ Problem:
if (condition) {
  useState();
}
πŸ‘‰ Leads to:
  • Bugs
  • State mismatch
  • Crashes

πŸ”Ή 2. Forgetting Dependencies in useEffect

useEffect(() => {
  fetchData();
}, []); // ❌ might miss dependencies
πŸ‘‰ Leads to stale data or unexpected behavior

πŸ”Ή 3. Returning Unstable Functions

return {
  increment: () => setCount(count + 1) // ❌ stale closure
};
βœ… Fix:
setCount(c => c + 1);

πŸ”Ή 4. Sharing State Accidentally (Wrong Assumption)

const a = useCounter();
const b = useCounter();
πŸ‘‰ These are independent Not shared unless you use context

πŸ”Ή 5. Memory Leaks (Async + Effects)

useEffect(() => {
  fetchData(); // ❌ may update unmounted component
}, []);
βœ… Fix:
  • Use cleanup
  • Track mounted state
  • Use AbortController

πŸ”Ή 6. Over-Abstraction

❌ Bad:
  • Creating hooks for trivial logic
πŸ‘‰ Leads to:
  • Hard-to-read code
  • Unnecessary complexity

5. βœ… Best Practices


πŸ”Ή 1. Follow Naming Convention

βœ” Must start with use
useAuth
useFetch
useForm
πŸ‘‰ Required for React to recognize hooks

πŸ”Ή 2. Keep Hooks Focused

βœ” One responsibility per hook ❌ Bad:
useEverything()
βœ” Good:
useFetch()
useAuth()
useForm()

πŸ”Ή 3. Extract Logic, Not UI

❌ Don’t:
  • Return JSX from hooks
βœ” Do:
  • Return data and functions only

πŸ”Ή 4. Use Functional Updates

Avoid stale state issues:
setState(prev => ...)

πŸ”Ή 5. Optimize Re-renders

Use:
  • useCallback
  • useMemo
Inside hooks when needed

πŸ”Ή 6. Handle Cleanup Properly

Always clean:
  • Event listeners
  • Timers
  • Subscriptions
return () => {
  // cleanup
};

πŸ”Ή 7. Avoid Side Effects Outside Hooks

❌ Wrong:
function useSomething() {
  fetch(); // outside useEffect ❌
}
βœ” Correct:
useEffect(() => {
  fetch();
}, []);

πŸ”Ή 8. Test Custom Hooks

Use libraries like:
  • React Testing Library
  • Jest
Test:
  • State changes
  • Side effects

πŸ”Ή 9. Document Your Hooks

Especially for teams:
/**
 * useFetch - Fetch data from API
 * @param {string} url
 */

πŸ”Ή 10. Think in Composition

πŸ‘‰ Combine hooks:
function useUserData() {
  const { data } = useFetch("/api/user");
  const isOnline = useOnlineStatus();

  return { data, isOnline };
}

🧠 Final Mental Model

  • Custom hooks are logic containers
  • They compose React hooks
  • They do not share state unless designed
  • They make your app:
    • Cleaner
    • Reusable
    • Scalable

🧠 Senior-Level React Interview β€” Custom Hooks (Conceptual Deep Dive)


1. What problem do custom hooks actually solve beyond β€œcode reuse”?

βœ… Strong Answer

Custom hooks are not just about reuse β€” they solve separation of concerns at the logic level. Before hooks:
  • Logic reuse required:
    • HOCs (Higher-Order Components)
    • Render props
  • These introduced:
    • Wrapper hell
    • Prop drilling complexity
    • Hard-to-trace data flow
πŸ‘‰ Custom hooks allow:
  • Extracting stateful logic without altering component structure
function useAuth() {
  const [user, setUser] = useState(null);
  // logic
  return user;
}

πŸ’‘ Why this matters

  • Keeps components focused on UI
  • Improves composability
  • Eliminates structural overhead (unlike HOCs)

πŸ” Comparison

ApproachProblem
HOCNested wrappers
Render PropsCallback nesting
Custom HooksFlat + composable βœ…

2. How does React internally track state for custom hooks?

βœ… Strong Answer

React does not treat custom hooks differently. Internally:
  • React maintains a linked list of hooks per component
  • Hooks are identified by call order, not name
useState();   // Hook #1
useEffect();  // Hook #2
Custom hook:
function useSomething() {
  useState(); // Hook #?
}
πŸ‘‰ React inlines this into the component’s hook list

πŸ’‘ Why this matters

  • Breaking order β†’ corrupts state mapping
  • Explains why hooks must:
    • Not be conditional
    • Not be inside loops

3. Why must custom hooks follow the β€œuse” naming convention?

βœ… Strong Answer

The use prefix is not stylistic β€” it’s required for:
  1. Linting enforcement (eslint-plugin-react-hooks)
  2. Static analysis to ensure:
    • Hooks are called correctly
    • Rules of hooks are followed
πŸ‘‰ React itself doesn’t enforce naming at runtime, but tooling does.

πŸ’‘ Why this matters

Without naming convention:
  • Bugs won’t be caught early
  • Hook misuse becomes silent

4. Do multiple components sharing a custom hook share state?

βœ… Strong Answer

No β€” custom hooks do NOT share state by default. Each call:
  • Creates an independent state instance
function useCounter() {
  const [count, setCount] = useState(0);
  return count;
}

const a = useCounter();
const b = useCounter();
πŸ‘‰ a and b are completely separate

πŸ’‘ Why

Because:
  • State is tied to the component instance
  • Not to the hook function

πŸ” To share state:

Use:
  • useContext
  • External stores (Redux, Zustand)

5. When should you NOT create a custom hook?

βœ… Strong Answer

Avoid custom hooks when:
  1. Logic is trivial
const doubled = count * 2;
  1. No reuse exists
  2. It increases abstraction without clarity

πŸ’‘ Why

Over-abstraction:
  • Makes debugging harder
  • Reduces readability
πŸ‘‰ Rule:
Extract only when it improves clarity OR reuse

6. How do custom hooks affect component re-renders?

βœ… Strong Answer

Custom hooks do not change React’s rendering behavior. Re-render happens when:
  • State changes
  • Props change
Inside hook:
const [state, setState] = useState();
πŸ‘‰ Updating this triggers parent component re-render

πŸ’‘ Important nuance

Returning new objects/functions:
return { value }; // new object every render ❌
πŸ‘‰ Causes unnecessary re-renders downstream

βœ… Fix

return useMemo(() => ({ value }), [value]);

7. How do you prevent stale closures in custom hooks?

βœ… Strong Answer

Stale closures occur when:
  • Functions capture outdated state
const increment = () => setCount(count + 1); // ❌

βœ… Fix: Functional updates

setCount(c => c + 1);

πŸ’‘ Why

  • Uses latest state snapshot
  • Avoids dependency issues

8. How should side effects be handled inside custom hooks?

βœ… Strong Answer

Always use useEffect (or similar) ❌ Wrong:
function useData() {
  fetch("/api"); // side effect outside hook lifecycle ❌
}
βœ… Correct:
useEffect(() => {
  fetch("/api");
}, []);

πŸ’‘ Why

  • Ensures lifecycle consistency
  • Prevents repeated or uncontrolled execution

9. How do you design a robust data-fetching custom hook?

βœ… Strong Answer

Key concerns:
  • Loading state
  • Error handling
  • Cancellation
  • Dependency control
function useFetch(url) {
  const [data, setData] = useState();
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(res => res.json())
      .then(setData)
      .catch(err => {
        if (err.name !== "AbortError") throw err;
      });

    return () => controller.abort();
  }, [url]);

  return { data, loading };
}

πŸ’‘ Why

Prevents:
  • Memory leaks
  • Race conditions

10. What are the trade-offs between custom hooks and context?

βœ… Strong Answer

AspectCustom HookContext
State sharingβŒβœ…
Reusabilityβœ…βš οΈ
PerformanceBetterCan cause re-renders
ScopeLocalGlobal

πŸ’‘ Insight

  • Custom hook = logic reuse
  • Context = state sharing
πŸ‘‰ Often used together

11. How do you compose multiple custom hooks safely?

βœ… Strong Answer

Hooks can call other hooks:
function useUser() {
  const data = useFetch("/user");
  const isOnline = useOnlineStatus();

  return { data, isOnline };
}

⚠️ Pitfall

  • Order must remain consistent

πŸ’‘ Why

Composition works because:
  • React flattens hook calls internally

12. What are common memory leak scenarios in custom hooks?

βœ… Strong Answer

  1. Async updates after unmount
  2. Uncleaned event listeners
  3. Timers not cleared
useEffect(() => {
  const id = setTimeout(() => {}, 1000);
  return () => clearTimeout(id);
}, []);

πŸ’‘ Why

React doesn’t auto-clean side effects

13. How do you make a custom hook configurable and reusable?

βœ… Strong Answer

Use parameters:
function useDebounce(value, delay = 300) {
Return flexible API:
return { value, cancel };

πŸ’‘ Why

  • Makes hook adaptable
  • Avoids rewriting logic

14. When should you use useReducer inside a custom hook?

βœ… Strong Answer

Use when:
  • State is complex
  • Multiple transitions exist
function useForm() {
  const [state, dispatch] = useReducer(reducer, initialState);
}

πŸ’‘ Why

  • Better state predictability
  • Easier debugging

15. How do custom hooks interact with Suspense and concurrent features?

βœ… Strong Answer

Custom hooks:
  • Can throw promises (for Suspense)
  • Must be idempotent (safe to re-run)

πŸ’‘ Why

Concurrent rendering:
  • May call hooks multiple times
  • Requires side effects to be well-controlled

16. What’s the risk of returning non-memoized functions from hooks?

βœ… Strong Answer

return { onClick: () => {} };
πŸ‘‰ New function every render β†’ breaks memoization

βœ… Fix

const onClick = useCallback(() => {}, []);
return { onClick };

πŸ’‘ Why

  • Prevents unnecessary child re-renders

17. How do you debug issues inside custom hooks?

βœ… Strong Answer

Techniques:
  • Add logs inside hook
  • Use React DevTools (inspect hooks)
  • Break logic into smaller hooks

πŸ’‘ Advanced

React DevTools shows:
  • Hook state per component instance

18. What is a β€œhook factory” and when would you use it?

βœ… Strong Answer

A function that returns a custom hook:
function createUseFeature(config) {
  return function useFeature() {
    return config;
  };
}

πŸ’‘ Use case

  • Dynamic behavior
  • Multi-tenant apps
  • Library design

πŸ”š Final Thought

At a senior level, custom hooks are not just about:
  • Reuse
They are about:
  • Designing clean abstractions
  • Managing complexity
  • Ensuring predictable behavior under React’s rendering model

🧠 Senior-Level MCQs β€” Custom Hooks in React


1. What happens if a custom hook is called conditionally inside a component?

if (isLoggedIn) {
  useAuth();
}

Options:

A. It works fine if condition is stable B. React throws a runtime error immediately C. It may cause incorrect state mapping across renders D. It only breaks in production builds

βœ… Correct Answer: C

πŸ’‘ Explanation:

React relies on hook call order, not names. Conditional calls shift the order, causing:
  • State mismatch
  • Unexpected bugs

❌ Why others are wrong:

  • A: Even stable conditions can change β†’ unsafe
  • B: React doesn’t always throw explicit errors
  • D: This issue is environment-independent

2. What is the real reason custom hooks must start with use?

Options:

A. React runtime uses it to identify hooks B. Babel transforms functions starting with use C. ESLint uses it to enforce Rules of Hooks D. It improves performance optimizations

βœ… Correct Answer: C

πŸ’‘ Explanation:

The use prefix enables static analysis via ESLint, ensuring:
  • Hooks are not used conditionally
  • Hooks follow rules

❌ Why others are wrong:

  • A: React runtime doesn’t rely on naming
  • B: Babel does not enforce hook behavior
  • D: No performance impact

3. Two components use the same custom hook. Will they share state?

Options:

A. Yes, always B. Only if hook uses useState C. No, each call creates independent state D. Only if wrapped in useMemo

βœ… Correct Answer: C

πŸ’‘ Explanation:

Each invocation creates a separate state instance tied to that component.

❌ Why others are wrong:

  • A: Incorrect assumption
  • B: useState doesn’t share state
  • D: useMemo doesn’t share state

4. What is the risk of returning a new object from a custom hook on every render?

return { value };

Options:

A. Memory leak B. Infinite loop C. Unnecessary re-renders in consuming components D. Hook order corruption

βœ… Correct Answer: C

πŸ’‘ Explanation:

New object reference each render β†’ breaks shallow comparison β†’ triggers re-renders.

❌ Why others are wrong:

  • A: No leak here
  • B: No loop unless used incorrectly
  • D: Hook order unaffected

5. What problem does this code introduce?

const increment = () => setCount(count + 1);

Options:

A. Memory leak B. Stale closure C. Infinite recursion D. Hook violation

βœ… Correct Answer: B

πŸ’‘ Explanation:

Function captures outdated count β†’ leads to incorrect updates.

❌ Why others are wrong:

  • A: No leak
  • C: No recursion
  • D: Hook rules not violated

6. What is the correct fix for stale closure?

Options:

A. setCount(count + 1) B. setCount(prev => prev + 1) C. useMemo(() => count + 1) D. useEffect(() => setCount(count + 1))

βœ… Correct Answer: B

πŸ’‘ Explanation:

Functional update ensures latest state is used.

❌ Why others are wrong:

  • A: Same stale issue
  • C: Not updating state
  • D: Incorrect pattern

7. Why is this problematic in a custom hook?

fetch("/api/data");
(outside useEffect)

Options:

A. Causes memory leak B. Runs only once C. Runs on every render causing side effects D. Breaks hook rules

βœ… Correct Answer: C

πŸ’‘ Explanation:

Code executes on every render β†’ uncontrolled side effects.

❌ Why others are wrong:

  • A: Not necessarily
  • B: Opposite behavior
  • D: Not a hook violation

8. What happens if a custom hook changes the order of internal hooks dynamically?

Options:

A. React ignores the change B. Causes incorrect state association C. Only affects performance D. Only affects nested hooks

βœ… Correct Answer: B

πŸ’‘ Explanation:

React maps hooks by order β†’ mismatch leads to bugs.

❌ Why others are wrong:

  • A: React doesn’t ignore
  • C: More than performance
  • D: Affects entire hook chain

9. What is the purpose of AbortController in a data-fetching hook?

Options:

A. Optimize fetch speed B. Cancel ongoing requests on unmount C. Retry failed requests D. Prevent duplicate state updates

βœ… Correct Answer: B

πŸ’‘ Explanation:

Prevents updating state after component unmount.

❌ Why others are wrong:

  • A: No speed gain
  • C: Not for retries
  • D: Not its primary purpose

10. What is the main trade-off between custom hooks and context?

Options:

A. Hooks are slower B. Context cannot share state C. Hooks don’t share state, context does D. Context cannot handle side effects

βœ… Correct Answer: C

πŸ’‘ Explanation:

Custom hooks = logic reuse Context = shared state

❌ Why others are wrong:

  • A: No inherent slowness
  • B: Context is for sharing
  • D: Context can be used with effects

11. What is a potential issue when returning functions from a custom hook?

Options:

A. Memory leak B. New function reference every render C. Hook rule violation D. State reset

βœ… Correct Answer: B

πŸ’‘ Explanation:

New references cause re-renders in dependent components.

❌ Why others are wrong:

  • A: No leak
  • C: Not a rule issue
  • D: State unaffected

12. Why should event listeners be cleaned up in custom hooks?

Options:

A. Improve performance B. Avoid duplicate API calls C. Prevent memory leaks D. Maintain hook order

βœ… Correct Answer: C

πŸ’‘ Explanation:

Unremoved listeners persist β†’ memory leaks.

❌ Why others are wrong:

  • A: Secondary effect
  • B: Not directly related
  • D: Not relevant

13. What is wrong with this hook?

function useSomething() {
  if (condition) {
    useEffect(() => {});
  }
}

Options:

A. Nothing B. Violates rules of hooks C. Causes memory leak D. Only fails in strict mode

βœ… Correct Answer: B

πŸ’‘ Explanation:

Hooks must not be conditional.

❌ Why others are wrong:

  • A: Incorrect
  • C: Not necessarily
  • D: Fails generally

14. Why might you use useReducer inside a custom hook?

Options:

A. Improve performance automatically B. Manage complex state transitions C. Replace useEffect D. Avoid re-renders

βœ… Correct Answer: B

πŸ’‘ Explanation:

Better suited for:
  • Multiple related state updates
  • Complex logic

❌ Why others are wrong:

  • A: Not guaranteed
  • C: Different purpose
  • D: Doesn’t prevent re-renders

15. What happens if you forget dependencies in a custom hook’s useEffect?

Options:

A. Nothing B. Infinite loop always C. Stale data or missed updates D. Hook crash

βœ… Correct Answer: C

πŸ’‘ Explanation:

Effect won’t react to changes β†’ stale values.

❌ Why others are wrong:

  • A: Incorrect
  • B: Only sometimes
  • D: No crash

16. What is a hook factory?

Options:

A. Hook that creates components B. Function returning a custom hook C. Hook that uses reducers D. Hook that caches data

βœ… Correct Answer: B

πŸ’‘ Explanation:

Factory generates customized hooks dynamically.

❌ Why others are wrong:

  • A: Not definition
  • C: Unrelated
  • D: Too specific

17. Why should custom hooks avoid returning JSX?

Options:

A. Performance issues B. Violates hook rules C. Breaks separation of concerns D. Causes memory leaks

βœ… Correct Answer: C

πŸ’‘ Explanation:

Hooks should manage logic, not UI.

❌ Why others are wrong:

  • A: Not primary reason
  • B: Not a rule violation
  • D: Not related

18. In concurrent rendering, what must custom hooks ensure?

Options:

A. They run only once B. Side effects are idempotent and controlled C. They don’t use state D. They avoid useEffect

βœ… Correct Answer: B

πŸ’‘ Explanation:

React may re-run hooks β†’ logic must be safe to repeat.

❌ Why others are wrong:

  • A: Not guaranteed
  • C: Incorrect
  • D: Wrong

πŸ”š Final Insight

These questions target:
  • Internal React behavior
  • Subtle bugs
  • Real-world design decisions
πŸ‘‰ Mastery here means: You’re not just using hooks β€” You understand how React thinks under the hood.

🧠 Custom Hooks β€” Real-World Coding Problems (Senior Level)


1. 🟑 Build useDebounce for Search Optimization

πŸ“Œ Problem

Create a hook that delays updating a value until a specified delay has passed.

Constraints

  • Should reset timer on value change
  • Must cleanup timers properly

Expected Behavior

Typing β€œReact” quickly β†’ only final value is returned after delay

Edge Cases

  • Rapid input changes
  • Component unmount during delay

βœ… Solution Explanation

  1. Store debounced value in state
  2. Use setTimeout in useEffect
  3. Cleanup previous timer
function useDebounce(value, delay) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);

  return debounced;
}

2. 🟑 Build useFetch with Cancellation

πŸ“Œ Problem

Create a data-fetching hook with loading and error states.

Constraints

  • Prevent state updates after unmount
  • Handle errors gracefully

Expected Behavior

  • loading true initially
  • data updates after fetch
  • No memory leaks

Edge Cases

  • URL changes quickly
  • Network failure

βœ… Solution Explanation

  • Use AbortController
  • Track loading + error
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(res => res.json())
      .then(setData)
      .catch(err => {
        if (err.name !== "AbortError") console.error(err);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [url]);

  return { data, loading };
}

3. 🟑 Build useLocalStorage

πŸ“Œ Problem

Sync state with localStorage.

Constraints

  • Persist across reloads
  • Sync updates

Expected Behavior

  • Value stored/retrieved from localStorage

Edge Cases

  • Invalid JSON
  • Key changes

βœ… Solution Explanation

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      return JSON.parse(localStorage.getItem(key)) ?? initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

4. 🟑 Build useWindowSize

πŸ“Œ Problem

Track window width and height.

Constraints

  • Must cleanup listeners

Expected Behavior

Updates on resize

Edge Cases

  • SSR (window undefined)

βœ… Solution

function useWindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    function update() {
      setSize({ width: window.innerWidth, height: window.innerHeight });
    }

    update();
    window.addEventListener("resize", update);

    return () => window.removeEventListener("resize", update);
  }, []);

  return size;
}

5. 🟑 Build useToggle

πŸ“Œ Problem

Toggle boolean state with utility functions

Expected Behavior

  • Toggle, setTrue, setFalse

Edge Cases

  • Multiple toggles quickly

βœ… Solution

function useToggle(initial = false) {
  const [state, setState] = useState(initial);

  const toggle = () => setState(s => !s);
  const setTrue = () => setState(true);
  const setFalse = () => setState(false);

  return { state, toggle, setTrue, setFalse };
}

6. 🟠 Build usePrevious

πŸ“Œ Problem

Store previous value of a variable

Expected Behavior

Returns previous render value

Edge Cases

  • First render β†’ undefined

βœ… Solution

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  });

  return ref.current;
}

7. 🟠 Build useOutsideClick

πŸ“Œ Problem

Detect clicks outside a component

Constraints

  • Use refs

Edge Cases

  • Multiple components using it

βœ… Solution

function useOutsideClick(ref, callback) {
  useEffect(() => {
    function handleClick(e) {
      if (!ref.current?.contains(e.target)) {
        callback();
      }
    }

    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);
  }, [ref, callback]);
}

8. 🟠 Build useOnlineStatus

πŸ“Œ Problem

Track online/offline state

Expected Behavior

  • Updates on network change

Edge Cases

  • Browser support

βœ… Solution

function useOnlineStatus() {
  const [online, setOnline] = useState(navigator.onLine);

  useEffect(() => {
    const on = () => setOnline(true);
    const off = () => setOnline(false);

    window.addEventListener("online", on);
    window.addEventListener("offline", off);

    return () => {
      window.removeEventListener("online", on);
      window.removeEventListener("offline", off);
    };
  }, []);

  return online;
}

9. πŸ”΄ Build useThrottle

πŸ“Œ Problem

Limit function execution frequency

Constraints

  • Should execute immediately then throttle

Edge Cases

  • Rapid calls

βœ… Solution

function useThrottle(value, delay) {
  const [throttled, setThrottled] = useState(value);
  const lastRun = useRef(Date.now());

  useEffect(() => {
    const now = Date.now();
    if (now - lastRun.current >= delay) {
      setThrottled(value);
      lastRun.current = now;
    }
  }, [value, delay]);

  return throttled;
}

10. πŸ”΄ Build useIntersectionObserver

πŸ“Œ Problem

Detect if element is visible in viewport

Constraints

  • Use IntersectionObserver API

Edge Cases

  • Browser fallback

βœ… Solution

function useIntersection(ref) {
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      setVisible(entry.isIntersecting);
    });

    if (ref.current) observer.observe(ref.current);

    return () => observer.disconnect();
  }, [ref]);

  return visible;
}

11. πŸ”΄ Build useForm (Reducer-based)

πŸ“Œ Problem

Manage complex form state

Constraints

  • Handle multiple fields

Edge Cases

  • Reset form

βœ… Solution

function useForm(initialState) {
  const reducer = (state, action) => {
    switch (action.type) {
      case "CHANGE":
        return { ...state, [action.name]: action.value };
      case "RESET":
        return initialState;
      default:
        return state;
    }
  };

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

  return {
    state,
    handleChange: e =>
      dispatch({ type: "CHANGE", name: e.target.name, value: e.target.value }),
    reset: () => dispatch({ type: "RESET" })
  };
}

12. πŸ”΄ Build useAsync

πŸ“Œ Problem

Handle async function lifecycle

Expected Behavior

  • loading, error, result

Edge Cases

  • Multiple calls overlap

βœ… Solution

function useAsync(fn) {
  const [state, setState] = useState({ loading: false });

  const run = async (...args) => {
    setState({ loading: true });
    try {
      const result = await fn(...args);
      setState({ loading: false, result });
    } catch (error) {
      setState({ loading: false, error });
    }
  };

  return { ...state, run };
}

13. πŸ”΄ Build useEventListener

πŸ“Œ Problem

Generic event listener hook

Constraints

  • Support dynamic target

Edge Cases

  • Changing handler

βœ… Solution

function useEventListener(event, handler, target = window) {
  useEffect(() => {
    target.addEventListener(event, handler);
    return () => target.removeEventListener(event, handler);
  }, [event, handler, target]);
}

14. πŸ”΄ Build useClipboard

πŸ“Œ Problem

Copy text to clipboard

Expected Behavior

Returns copy function + success state

Edge Cases

  • Permission denied

βœ… Solution

function useClipboard() {
  const [copied, setCopied] = useState(false);

  const copy = async text => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
    } catch {
      setCopied(false);
    }
  };

  return { copied, copy };
}

15. πŸ”΄ Build useIdle

πŸ“Œ Problem

Detect user inactivity

Constraints

  • Reset timer on activity

Edge Cases

  • Multiple event listeners

βœ… Solution

function useIdle(timeout = 3000) {
  const [idle, setIdle] = useState(false);

  useEffect(() => {
    let timer;

    const reset = () => {
      clearTimeout(timer);
      setIdle(false);
      timer = setTimeout(() => setIdle(true), timeout);
    };

    window.addEventListener("mousemove", reset);
    window.addEventListener("keydown", reset);

    reset();

    return () => {
      window.removeEventListener("mousemove", reset);
      window.removeEventListener("keydown", reset);
    };
  }, [timeout]);

  return idle;
}

16. πŸ”΄ Build useUndoRedo

πŸ“Œ Problem

Implement undo/redo functionality

Constraints

  • Maintain history stack

Edge Cases

  • Limit history size

βœ… Solution

function useUndoRedo(initial) {
  const [history, setHistory] = useState([initial]);
  const [index, setIndex] = useState(0);

  const set = value => {
    const newHistory = history.slice(0, index + 1);
    newHistory.push(value);
    setHistory(newHistory);
    setIndex(newHistory.length - 1);
  };

  return {
    state: history[index],
    set,
    undo: () => index > 0 && setIndex(i => i - 1),
    redo: () => index < history.length - 1 && setIndex(i => i + 1)
  };
}

17. πŸ”΄ Build usePagination

πŸ“Œ Problem

Handle paginated data

Constraints

  • Page navigation

Edge Cases

  • Out-of-bound pages

βœ… Solution

function usePagination(data, perPage) {
  const [page, setPage] = useState(1);

  const max = Math.ceil(data.length / perPage);

  const current = data.slice((page - 1) * perPage, page * perPage);

  return {
    current,
    next: () => setPage(p => Math.min(p + 1, max)),
    prev: () => setPage(p => Math.max(p - 1, 1))
  };
}

18. πŸ”΄ Build useHover

πŸ“Œ Problem

Detect hover state

Edge Cases

  • Mobile devices

βœ… Solution

function useHover() {
  const [hovered, setHovered] = useState(false);

  const ref = useRef();

  useEffect(() => {
    const node = ref.current;
    if (!node) return;

    const enter = () => setHovered(true);
    const leave = () => setHovered(false);

    node.addEventListener("mouseenter", enter);
    node.addEventListener("mouseleave", leave);

    return () => {
      node.removeEventListener("mouseenter", enter);
      node.removeEventListener("mouseleave", leave);
    };
  }, []);

  return [ref, hovered];
}

πŸ”š Final Insight

These problems test:
  • Real-world thinking
  • Lifecycle awareness
  • Cleanup discipline
  • Performance considerations
πŸ‘‰ Mastery means: You can design hooks like a library author, not just use them.

πŸ› οΈ Senior React Code Review β€” Custom Hooks Debugging Challenges


1. ❗ Stale Closure in Counter Hook

function useCounter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return { count, increment };
}

πŸ” What’s wrong?

increment uses a stale value of count

πŸ’‘ Why it happens

Closures capture the value at render time. Multiple rapid updates may use outdated count.

βœ… Fix

const increment = () => {
  setCount(c => c + 1);
};

🧠 Best Practice

Always use functional updates when new state depends on previous state.

2. ❗ Memory Leak in Fetch Hook

function useFetch(url) {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(setData);
  }, [url]);

  return data;
}

πŸ” What’s wrong?

State may update after component unmounts.

πŸ’‘ Why

Fetch is async; component may unmount before it completes.

βœ… Fix

useEffect(() => {
  const controller = new AbortController();

  fetch(url, { signal: controller.signal })
    .then(res => res.json())
    .then(setData)
    .catch(err => {
      if (err.name !== "AbortError") throw err;
    });

  return () => controller.abort();
}, [url]);

🧠 Best Practice

Always cancel async operations in effects.

3. ❗ Missing Dependency Causing Stale Data

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

  useEffect(() => {
    fetch(`/api?q=${query}`).then(res => res.json()).then(setResults);
  }, []); // ❌

  return results;
}

πŸ” What’s wrong?

Effect does not re-run when query changes.

πŸ’‘ Why

Empty dependency array freezes the initial query.

βœ… Fix

useEffect(() => {
  fetch(`/api?q=${query}`)
    .then(res => res.json())
    .then(setResults);
}, [query]);

🧠 Best Practice

Always include all external dependencies.

4. ❗ Event Listener Re-Attached Every Render

function useResize(handler) {
  useEffect(() => {
    window.addEventListener("resize", handler);
    return () => window.removeEventListener("resize", handler);
  });
}

πŸ” What’s wrong?

Effect runs on every render β†’ repeated add/remove.

πŸ’‘ Why

No dependency array β†’ runs after every render.

βœ… Fix

useEffect(() => {
  window.addEventListener("resize", handler);
  return () => window.removeEventListener("resize", handler);
}, [handler]);

🧠 Best Practice

Always define effect dependencies explicitly.

5. ❗ Infinite Loop in Hook

function useData(url) {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(url).then(res => res.json()).then(setData);
  }, [data]); // ❌

  return data;
}

πŸ” What’s wrong?

Effect depends on data, which it updates β†’ infinite loop.

πŸ’‘ Why

State update triggers effect again.

βœ… Fix

useEffect(() => {
  fetch(url).then(res => res.json()).then(setData);
}, [url]);

🧠 Best Practice

Dependencies should reflect inputs, not outputs.

6. ❗ Unstable Function Causing Re-Renders

function useActions() {
  const [count, setCount] = useState(0);

  return {
    increment: () => setCount(c => c + 1)
  };
}

πŸ” What’s wrong?

New function instance on every render.

πŸ’‘ Why

Functions are recreated β†’ breaks memoization.

βœ… Fix

const increment = useCallback(() => {
  setCount(c => c + 1);
}, []);

return { increment };

🧠 Best Practice

Memoize functions when passed to children.

7. ❗ Incorrect Ref Usage

function usePrevious(value) {
  const ref = useRef(value);

  return ref.current;
}

πŸ” What’s wrong?

Ref is never updated.

πŸ’‘ Why

useRef doesn’t auto-update like state.

βœ… Fix

useEffect(() => {
  ref.current = value;
});

🧠 Best Practice

Update refs inside effects.

8. ❗ Breaking Hook Rules

function useFeature(flag) {
  if (flag) {
    useEffect(() => {
      console.log("Enabled");
    }, []);
  }
}

πŸ” What’s wrong?

Hook is conditionally called.

πŸ’‘ Why

Breaks hook order β†’ undefined behavior.

βœ… Fix

useEffect(() => {
  if (flag) {
    console.log("Enabled");
  }
}, [flag]);

🧠 Best Practice

Never call hooks conditionally.

9. ❗ Race Condition in Fetch

function useUser(id) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/user/${id}`).then(res => res.json()).then(setUser);
  }, [id]);
}

πŸ” What’s wrong?

Older request may overwrite newer one.

πŸ’‘ Why

Async responses return out of order.

βœ… Fix

useEffect(() => {
  let active = true;

  fetch(`/user/${id}`)
    .then(res => res.json())
    .then(data => {
      if (active) setUser(data);
    });

  return () => {
    active = false;
  };
}, [id]);

🧠 Best Practice

Handle race conditions in async hooks.

10. ❗ Derived State Stored Incorrectly

function useFullName(first, last) {
  const [fullName, setFullName] = useState("");

  useEffect(() => {
    setFullName(first + " " + last);
  }, [first, last]);

  return fullName;
}

πŸ” What’s wrong?

Derived state stored unnecessarily.

πŸ’‘ Why

Adds extra render cycle.

βœ… Fix

return `${first} ${last}`;

🧠 Best Practice

Avoid storing derived state.

11. ❗ Missing Cleanup for Timer

function useTimer() {
  useEffect(() => {
    setInterval(() => console.log("tick"), 1000);
  }, []);
}

πŸ” What’s wrong?

Interval never cleared.

πŸ’‘ Why

Leads to memory leaks.

βœ… Fix

useEffect(() => {
  const id = setInterval(() => console.log("tick"), 1000);
  return () => clearInterval(id);
}, []);

🧠 Best Practice

Always cleanup timers.

12. ❗ Using State Instead of Ref for Mutable Value

function useRenderCount() {
  const [count, setCount] = useState(0);

  setCount(c => c + 1);

  return count;
}

πŸ” What’s wrong?

State update inside render β†’ infinite loop.

πŸ’‘ Why

State updates trigger re-render immediately.

βœ… Fix

const ref = useRef(0);
ref.current++;
return ref.current;

🧠 Best Practice

Use useRef for non-render-triggering values.

13. ❗ Over-Fetching Due to Object Dependency

function useFilter(data, filter) {
  useEffect(() => {
    // expensive logic
  }, [filter]);
}

πŸ” What’s wrong?

filter is an object β†’ new reference each render.

πŸ’‘ Why

Triggers unnecessary effect runs.

βœ… Fix

const stableFilter = useMemo(() => filter, [JSON.stringify(filter)]);

🧠 Best Practice

Stabilize object dependencies.

14. ❗ Hook Not SSR Safe

function useWidth() {
  return window.innerWidth;
}

πŸ” What’s wrong?

Breaks in SSR (window undefined).

πŸ’‘ Why

Server doesn’t have window.

βœ… Fix

const [width, setWidth] = useState(
  typeof window !== "undefined" ? window.innerWidth : 0
);

🧠 Best Practice

Guard browser APIs in hooks.

15. ❗ Incorrect Dependency on Function

function useSomething(fn) {
  useEffect(() => {
    fn();
  }, [fn]);
}

πŸ” What’s wrong?

Effect runs every render if fn is unstable.

πŸ’‘ Why

Functions are recreated unless memoized.

βœ… Fix

  • Either memoize fn in parent OR
  • Remove from deps if safe

🧠 Best Practice

Stabilize callbacks using useCallback.

16. ❗ State Reset on Key Change

function useKeyedState(key) {
  const [value, setValue] = useState(0);
  return [value, setValue];
}

πŸ” What’s wrong?

State does not reset when key changes.

πŸ’‘ Why

useState only initializes once.

βœ… Fix

useEffect(() => {
  setValue(0);
}, [key]);

🧠 Best Practice

Handle prop-driven resets explicitly.

17. ❗ Multiple Event Listeners Leak

function useScroll() {
  useEffect(() => {
    window.addEventListener("scroll", () => console.log("scroll"));
  }, []);
}

πŸ” What’s wrong?

Anonymous function cannot be removed.

πŸ’‘ Why

Cleanup needs same reference.

βœ… Fix

useEffect(() => {
  const handler = () => console.log("scroll");
  window.addEventListener("scroll", handler);
  return () => window.removeEventListener("scroll", handler);
}, []);

🧠 Best Practice

Always store handler references.

πŸ”š Final Takeaway

These bugs reflect real production failures:
  • Stale closures
  • Race conditions
  • Memory leaks
  • Dependency mistakes
πŸ‘‰ Senior-level skill = Not just writing hooks, but predicting React behavior under pressure.

🧠 Senior Frontend Architect β€” Custom Hooks Machine Coding Problems


1. πŸ”΄ Build a Production-Grade Search with Debounce + Caching

πŸ“Œ Requirements

  • Input box with live search
  • API call after user stops typing (debounce)
  • Cache previous results (avoid duplicate API calls)

πŸ–₯️ UI Behavior

  • Typing β†’ loading spinner
  • Cached results β†’ instant display
  • Empty query β†’ no API call

πŸ”„ State/Data Flow

  • query β†’ debounced β†’ fetch β†’ cache β†’ render

⚠️ Edge Cases

  • Rapid typing
  • Same query repeated
  • API failure

⚑ Performance

  • Debounce (300–500ms)
  • Cache results (Map or object)

πŸ—οΈ Suggested Architecture

  • useDebounce(query)
  • useSearch(query) (handles caching + API)

πŸͺœ Solution Approach

  1. Create debounce hook
  2. Maintain cache with useRef
  3. Fetch only if query not cached
  4. Handle loading + error states

2. πŸ”΄ Build a Dynamic Form Builder (Schema-Driven)

πŸ“Œ Requirements

  • Render form based on JSON schema
  • Support validation & dynamic fields

πŸ–₯️ UI Behavior

  • Fields appear/disappear based on conditions
  • Show validation errors on submit

πŸ”„ State/Data Flow

  • Schema β†’ generate fields β†’ state via reducer

⚠️ Edge Cases

  • Nested fields
  • Dynamic validation rules
  • Reset form

⚑ Performance

  • Avoid re-rendering all fields
  • Memoize field components

πŸ—οΈ Architecture

  • useForm(schema)
  • useField(name)
  • useValidation()

πŸͺœ Approach

  1. Use useReducer for form state
  2. Store schema separately
  3. Generate fields dynamically
  4. Validate on change/submit

3. πŸ”΄ Build Infinite Scroll with Virtualization

πŸ“Œ Requirements

  • Load items as user scrolls
  • Render only visible items

πŸ–₯️ UI Behavior

  • Scroll β†’ fetch next page
  • Smooth scrolling

πŸ”„ State/Data Flow

  • Scroll position β†’ trigger fetch β†’ append data

⚠️ Edge Cases

  • Fast scrolling
  • Duplicate fetches
  • End of list

⚑ Performance

  • Virtualize list (windowing)
  • Throttle scroll events

πŸ—οΈ Architecture

  • useInfiniteScroll()
  • useVirtualList()

πŸͺœ Approach

  1. Track scroll position
  2. Trigger fetch near bottom
  3. Use refs for container height
  4. Render subset of items

4. πŸ”΄ Build Global Auth System with Custom Hook

πŸ“Œ Requirements

  • Login/logout
  • Persist user session
  • Protect routes

πŸ–₯️ UI Behavior

  • Redirect unauthenticated users
  • Show user info globally

πŸ”„ State/Data Flow

  • Auth state β†’ context β†’ components

⚠️ Edge Cases

  • Token expiry
  • Refresh token logic

⚑ Performance

  • Avoid unnecessary re-renders

πŸ—οΈ Architecture

  • useAuth() + Context
  • useProtectedRoute()

πŸͺœ Approach

  1. Store token in localStorage
  2. Sync with context
  3. Handle login/logout actions
  4. Add route guard hook

5. πŸ”΄ Build Real-Time Chat Hook (WebSocket)

πŸ“Œ Requirements

  • Send/receive messages in real-time

πŸ–₯️ UI Behavior

  • Messages update instantly
  • Connection status indicator

πŸ”„ State/Data Flow

  • WebSocket β†’ message stream β†’ UI

⚠️ Edge Cases

  • Connection drops
  • Message ordering

⚑ Performance

  • Batch updates if needed

πŸ—οΈ Architecture

  • useWebSocket(url)
  • useChat(roomId)

πŸͺœ Approach

  1. Initialize socket in hook
  2. Listen for messages
  3. Update state safely
  4. Cleanup on unmount

6. πŸ”΄ Build Undo/Redo Editor System

πŸ“Œ Requirements

  • Track history of user actions
  • Undo/redo support

πŸ–₯️ UI Behavior

  • Undo button reverts state
  • Redo reapplies changes

πŸ”„ State/Data Flow

  • Current state + history stack

⚠️ Edge Cases

  • Limit history size
  • Rapid updates

⚑ Performance

  • Avoid large memory usage

πŸ—οΈ Architecture

  • useUndoRedo()

πŸͺœ Approach

  1. Maintain history array
  2. Track pointer index
  3. Slice history on new action
  4. Implement undo/redo safely

7. πŸ”΄ Build Advanced Data Fetching Hook (React Query Lite)

πŸ“Œ Requirements

  • Cache API responses
  • Background refetch
  • Deduplicate requests

πŸ–₯️ UI Behavior

  • Instant cached data
  • Refetch indicator

πŸ”„ State/Data Flow

  • Query key β†’ cache β†’ fetch

⚠️ Edge Cases

  • Stale data
  • Parallel requests

⚑ Performance

  • Cache + deduplication

πŸ—οΈ Architecture

  • useQuery(key, fetchFn)
  • Global cache store

πŸͺœ Approach

  1. Use Map for cache
  2. Track in-flight requests
  3. Return cached data immediately
  4. Refetch in background

8. πŸ”΄ Build Drag-and-Drop Hook

πŸ“Œ Requirements

  • Drag items between lists

πŸ–₯️ UI Behavior

  • Visual feedback while dragging

πŸ”„ State/Data Flow

  • Drag state β†’ drop target β†’ update list

⚠️ Edge Cases

  • Dropping outside
  • Reordering

⚑ Performance

  • Avoid excessive re-renders

πŸ—οΈ Architecture

  • useDrag()
  • useDrop()

πŸͺœ Approach

  1. Track dragged item with ref
  2. Handle drag events
  3. Update state on drop
  4. Cleanup listeners

9. πŸ”΄ Build Feature Flag System

πŸ“Œ Requirements

  • Enable/disable features dynamically

πŸ–₯️ UI Behavior

  • Features toggle without reload

πŸ”„ State/Data Flow

  • Flags β†’ hook β†’ components

⚠️ Edge Cases

  • Async flag loading
  • Fallback values

⚑ Performance

  • Avoid re-rendering entire app

πŸ—οΈ Architecture

  • useFeatureFlag(flagName)

πŸͺœ Approach

  1. Store flags globally
  2. Fetch from API
  3. Provide via context
  4. Hook reads specific flag

10. πŸ”΄ Build Viewport-Based Lazy Loading System

πŸ“Œ Requirements

  • Load images/components only when visible

πŸ–₯️ UI Behavior

  • Placeholder β†’ load on scroll into view

πŸ”„ State/Data Flow

  • Intersection β†’ load trigger β†’ render

⚠️ Edge Cases

  • Fast scroll
  • Multiple elements

⚑ Performance

  • Use IntersectionObserver

πŸ—οΈ Architecture

  • useInView(ref)
  • useLazyLoad()

πŸͺœ Approach

  1. Observe element visibility
  2. Trigger load when visible
  3. Unobserve after load
  4. Cache loaded state

πŸ”š Final Insight

These problems simulate:
  • Real product features
  • Performance-critical systems
  • Scalable architecture design
πŸ‘‰ At senior level, evaluation focuses on:
  • Hook design quality
  • State management clarity
  • Edge-case handling
  • Performance awareness

🧠 Senior Frontend Interview β€” Custom Hooks (Deep Dive)


1. How would you decide whether to extract logic into a custom hook vs keeping it inside a component?

πŸ” Follow-up:

  • What signals indicate over-abstraction?
  • How does team size affect this decision?

βœ… Strong Answer:

  • Extract when:
    • Logic is reused across components
    • Component becomes too complex (logic/UI mixed)
  • Avoid when:
    • Logic is trivial or used once
  • Consider:
    • Readability vs reuse trade-off
    • Debuggability

❌ Weak Answer:

β€œWhenever there is duplicate code”
πŸ‘‰ Fails because:
  • Ignores complexity, readability, and maintainability trade-offs

2. Explain how React tracks hook state internally and why order matters.

πŸ” Follow-up:

  • What happens if order changes?
  • Can React detect this reliably?

βœ… Strong Answer:

  • React uses a linked list of hooks per component
  • Hooks are mapped by call order
  • Changing order β†’ state mismatch β†’ bugs

❌ Weak Answer:

β€œReact uses hook names”
πŸ‘‰ Fails because:
  • Completely incorrect mental model

3. Design a useFetch hook for production use. What concerns would you handle?

πŸ” Follow-up:

  • How do you prevent race conditions?
  • How do you cache results?

βœ… Strong Answer:

Must include:
  • Loading/error state
  • AbortController (cleanup)
  • Race condition handling
  • Optional caching layer
  • Dependency management

❌ Weak Answer:

β€œJust use useEffect and fetch”
πŸ‘‰ Fails because:
  • Ignores real-world issues like cancellation and caching

4. Why don’t custom hooks share state across components?

πŸ” Follow-up:

  • How would you make them share state?

βœ… Strong Answer:

  • State is tied to component instance
  • Each hook call creates a new state
  • Share via:
    • Context
    • External store

❌ Weak Answer:

β€œBecause hooks are isolated”
πŸ‘‰ Fails because:
  • Doesn’t explain underlying reason

5. You notice a custom hook causing excessive re-renders. How would you debug it?

πŸ” Follow-up:

  • What tools would you use?
  • What patterns cause this?

βœ… Strong Answer:

Steps:
  1. Check returned values (new objects/functions)
  2. Inspect dependencies
  3. Use React DevTools profiler
  4. Add memoization (useMemo, useCallback)

❌ Weak Answer:

β€œUse memo everywhere”
πŸ‘‰ Fails because:
  • Blind optimization without diagnosis

6. What are common pitfalls when using useEffect inside custom hooks?

πŸ” Follow-up:

  • How do you avoid stale closures?

βœ… Strong Answer:

  • Missing dependencies β†’ stale data
  • Over-dependencies β†’ unnecessary runs
  • Async side effects β†’ memory leaks
  • Fix:
    • Dependency discipline
    • Cleanup functions
    • Functional updates

❌ Weak Answer:

β€œJust follow ESLint”
πŸ‘‰ Fails because:
  • Doesn’t explain reasoning

7. How would you design a custom hook that is both flexible and reusable?

πŸ” Follow-up:

  • How do you design its API?

βœ… Strong Answer:

  • Accept parameters for configuration
  • Return minimal but useful API
  • Avoid tight coupling
  • Example:
useDebounce(value, delay)

❌ Weak Answer:

β€œMake it generic”
πŸ‘‰ Fails because:
  • Too vague, no implementation thinking

8. Explain a scenario where a custom hook introduces a bug due to stale closures.

πŸ” Follow-up:

  • How do you fix it?

βœ… Strong Answer:

  • Event handlers capturing old state
  • Fix with:
    • Functional updates
    • Correct dependencies

❌ Weak Answer:

β€œClosures are tricky”
πŸ‘‰ Fails because:
  • No concrete understanding

9. When would you use useReducer inside a custom hook?

πŸ” Follow-up:

  • Why not just use useState?

βœ… Strong Answer:

  • Complex state transitions
  • Multiple related states
  • Better predictability

❌ Weak Answer:

β€œFor big state”
πŸ‘‰ Fails because:
  • Lacks clarity on why

10. How do you handle race conditions in async hooks?

πŸ” Follow-up:

  • What if requests return out of order?

βœ… Strong Answer:

  • Use:
    • AbortController OR
    • β€œisActive” flag
  • Ensure only latest request updates state

❌ Weak Answer:

β€œUse try/catch”
πŸ‘‰ Fails because:
  • Doesn’t solve race conditions

11. What are the trade-offs between custom hooks and third-party libraries like React Query?

πŸ” Follow-up:

  • When would you build vs buy?

βœ… Strong Answer:

  • Custom hooks:
    • Lightweight, flexible
    • Require maintenance
  • Libraries:
    • Feature-rich (caching, retries)
    • More abstraction

❌ Weak Answer:

β€œLibraries are always better”
πŸ‘‰ Fails because:
  • Ignores trade-offs

12. How would you design a hook that works in SSR?

πŸ” Follow-up:

  • What breaks in SSR?

βœ… Strong Answer:

  • Avoid direct browser APIs
  • Use guards:
typeof window !== "undefined"

❌ Weak Answer:

β€œSSR just works”
πŸ‘‰ Fails because:
  • Ignores environment differences

13. How do you prevent memory leaks in custom hooks?

πŸ” Follow-up:

  • Examples?

βœ… Strong Answer:

  • Cleanup:
    • Event listeners
    • Timers
    • Async calls

❌ Weak Answer:

β€œReact handles cleanup”
πŸ‘‰ Fails because:
  • Incorrect assumption

14. How do custom hooks behave in concurrent rendering?

πŸ” Follow-up:

  • What changes compared to legacy rendering?

βœ… Strong Answer:

  • Hooks may run multiple times
  • Must be:
    • Idempotent
    • Side-effect safe

❌ Weak Answer:

β€œNo difference”
πŸ‘‰ Fails because:
  • Misses modern React behavior

15. How would you design a useQuery-like hook?

πŸ” Follow-up:

  • How do you cache and deduplicate?

βœ… Strong Answer:

  • Use:
    • Cache map
    • In-flight request tracking
    • Background refetch

❌ Weak Answer:

β€œCall fetch and store data”
πŸ‘‰ Fails because:
  • Misses system-level thinking

16. What’s the risk of returning non-memoized values from hooks?

πŸ” Follow-up:

  • When is it acceptable?

βœ… Strong Answer:

  • Causes re-renders
  • Acceptable if:
    • Not passed to children
    • No performance concern

❌ Weak Answer:

β€œAlways memoize”
πŸ‘‰ Fails because:
  • Over-optimization mindset

17. How would you debug a hook that works locally but fails in production?

πŸ” Follow-up:

  • What differences matter?

βœ… Strong Answer:

  • Check:
    • Strict mode double render
    • Environment differences
    • Race conditions
    • Minification issues

❌ Weak Answer:

β€œConsole.log”
πŸ‘‰ Fails because:
  • Too shallow

18. How do you compose multiple hooks safely?

πŸ” Follow-up:

  • What risks exist?

βœ… Strong Answer:

  • Call hooks in consistent order
  • Avoid conditional composition
  • Combine outputs cleanly

❌ Weak Answer:

β€œJust call them”
πŸ‘‰ Fails because:
  • Ignores rules of hooks

19. When can a custom hook hurt performance?

πŸ” Follow-up:

  • Real examples?

βœ… Strong Answer:

  • Frequent re-renders
  • Expensive computations inside hook
  • Unstable dependencies

❌ Weak Answer:

β€œHooks are fast”
πŸ‘‰ Fails because:
  • Oversimplified

πŸ”š Final Insight

At senior level, interviewers evaluate:
  • Mental model of React internals
  • Ability to design scalable abstractions
  • Understanding of trade-offs
  • Debugging under real-world constraints
πŸ‘‰ Strong candidates don’t just use hooks β€” They design, analyze, and optimize them like systems.