Master React Hooks – Interview Q&A

101

What are the Rules of Hooks in React?

  1. Only call Hooks at the top level — never inside loops, conditions, nested functions, or after early returns.
  2. Only call Hooks from React function components or custom Hooks (not regular JS functions).
  3. Use the ESLint plugin eslint-plugin-react-hooks — it catches most violations automatically.

Reason: React relies on the order of hook calls to correctly associate each hook with its state / effect / ref memory between renders.

102

Why can't we call useState / useEffect inside if / else or loops?

Because the number and order of hook calls must be exactly the same on every render.

If a condition / loop changes between renders, some hooks might be skipped → React loses track of which state belongs to which hook → bugs, crashes or wrong values.

Solution: move conditional logic inside the hook:

useEffect(() => {
  if (!isOpen) return;
  // effect logic
}, [isOpen]);
103

What is the lazy initializer function in useState? Show example.

The function passed to useState runs only once (on mount) → useful for expensive calculations or reading from localStorage.

const [user, setUser] = useState(() => {
  const saved = localStorage.getItem('user');
  return saved ? JSON.parse(saved) : null;
});
104

Match these useEffect patterns with class lifecycle methods

  • useEffect(fn, []) → componentDidMount
  • useEffect(fn, [dep1, dep2]) → componentDidUpdate (only when deps change)
  • return () => cleanup() → componentWillUnmount
  • useEffect(fn) (no array) → componentDidMount + componentDidUpdate
105

Name four common cleanup patterns in useEffect

  • return () => clearInterval(timerId);
  • return () => window.removeEventListener('resize', handler);
  • const controller = new AbortController(); ... return () => controller.abort();
  • return () => subscription.unsubscribe(); (RxJS, Firebase, etc.)
106

What happens if you forget the dependency array in useEffect?

Effect runs after every render (including after setState inside it) → very often causes infinite render loops.

Only safe when you intentionally want to run code on every render (rare).

107

useEffect vs useLayoutEffect – when to choose each

Hook Timing Blocking Use when
useEffect after paint no data fetching, timers, most side effects
useLayoutEffect before paint yes DOM measurements, prevent visual flicker, scroll adjustments

Rule: default = useEffect. Use useLayoutEffect only if you see layout shift / flicker.

108

What is the primary purpose of useMemo?

Skip expensive calculations on re-renders when dependencies have not changed.

const sortedUsers = useMemo(() => {
  return [...users].sort((a,b) => a.name.localeCompare(b.name));
}, [users]);
109

When is useCallback actually useful? (3 main cases)

  1. Passing stable callback to React.memo child component
  2. Passing function as dependency to another hook (useEffect, useMemo)
  3. Avoiding unnecessary effect triggers when function reference changes
110

useMemo vs useCallback – table comparison

useMemo useCallback
Memoizes any value function only
Return type whatever factory returns function
Common use expensive derived data, objects, arrays stable callbacks for children / deps
111

What exactly does React.memo compare? (shallow or deep?)

Shallow comparison (Object.is) of all props.

Objects / arrays / functions are compared by reference → new object = re-render even if content is same.

112

Name 5 real use-cases for useRef (not just DOM access)

  • Store previous value of state / prop
  • Mutable value that doesn’t cause re-render (interval ID, socket, etc.)
  • Check if component is still mounted before setState
  • Store latest callback reference (avoid stale closures)
  • Imperative focus / scroll / animation control
113

How can you access the previous value of a state variable?

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

const prev = usePrevious(count);
114

When is useReducer better than multiple useState calls?

  • Complex state object with multiple related fields
  • State updates have many conditional branches
  • Want to centralize business logic in one place (reducer)
  • Easier to test state transitions
  • Want Redux-like pattern without external library
115

Basic useReducer counter example (increment / decrement / reset)

const initialState = { count: 0 };

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;
  }
}

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

What is the third argument (init function) in useReducer for?

Lazy initialization — runs only once, even if component re-renders.

const [state, dispatch] = useReducer(reducer, initialArg, (arg) => expensiveSetup(arg));
117

What does useContext actually return?

The current context value from the nearest <Context.Provider> above in the tree.

If no provider → returns the default value passed to createContext(defaultValue).

118

Why do we pass a default value to createContext?

  • Fallback when component is rendered outside any Provider (useful in tests)
  • Helps TypeScript infer types
  • Can provide sensible defaults (theme = 'light', user = null, etc.)
119

What problem does useImperativeHandle solve?

Allows a component to expose custom imperative methods to its parent via ref (instead of exposing the whole DOM node or instance).

Common: focus(), scrollToBottom(), play(), etc.

121

What is useTransition? (React 18+)

Marks state updates as non-urgent (transitions).

Allows React to keep showing old UI while preparing new content in background.

const [isPending, startTransition] = useTransition();
startTransition(() => setTab('profile'));
122

useDeferredValue vs useTransition – main difference

Hook Defers Typical usage
useTransition state update tab switches, filtering large lists, navigation
useDeferredValue value search input → expensive filtered list
123

What problem does useId solve? (React 18+)

Generates unique IDs that are stable across server and client → prevents hydration mismatch warnings.

const id = useId();
<label htmlFor={id}>Email</label>
<input id={id} />
124

What is useSyncExternalStore? (when / why used)

Low-level hook to safely subscribe to external mutable stores (browser APIs, third-party state) in concurrent mode without tearing.

Used internally by:

  • zustand
  • recoil
  • many react-query-like libraries
125

What are the only two naming rules for custom hooks?

  1. Name must start with "use" (useFetch, useWindowSize, etc.)
  2. Must call other hooks inside (otherwise ESLint will complain)
126

Name 6 popular / useful custom hooks

  • useFetch / useApi
  • useLocalStorage
  • useDebounce
  • useMediaQuery
  • usePrevious
  • useOnClickOutside
  • useWindowSize
  • useForm
127

What problem does the "stale closure" cause in hooks? How to fix?

Callback / effect captures old value because function was created in previous render.

Fixes:

  • use useRef for mutable latest value
  • use functional setState
  • put value in dependency array
  • use useCallback + ref pattern for callbacks
128

Common pattern: "isMounted" ref to prevent setState on unmounted component

function useIsMounted() {
  const isMounted = useRef(false);
  useEffect(() => {
    isMounted.current = true;
    return () => { isMounted.current = false; };
  }, []);
  return isMounted;
}

// usage
const isMounted = useIsMounted();
useEffect(() => {
  fetchData().then(data => {
    if (isMounted.current) setData(data);
  });
}, []);
129

How do you test custom hooks? (library name & basic pattern)

Use @testing-library/react-hooks (now part of @testing-library/react)

import { renderHook, act } from '@testing-library/react';

test('counter increments', () => {
  const { result } = renderHook(() => useCounter());
  act(() => result.current.increment());
  expect(result.current.count).toBe(1);
});
130

What is useOptimistic? (React 19)

Simplifies optimistic UI updates – shows final state immediately, auto-reverts on error.

const [optimisticLikes, addOptimisticLike] = useOptimistic(likes);

async function handleLike() {
  addOptimisticLike(likes + 1);
  await api.like();
}
131

Common infinite loop causes in useEffect (name 4)

  • Missing dependency array
  • Object / array literal in deps ({}, [])
  • Calling setState unconditionally inside effect
  • New function / object created every render in deps
132

Why does putting object in useEffect deps usually cause problems?

New object reference created every render → effect thinks deps changed → runs every time.

Fix: use useMemo or move object creation inside effect.

133

How to memoize an object or array with useMemo?

const filters = useMemo(() => ({
  status: 'active',
  sort: 'desc'
}), []);   // empty deps = created once
134

Simple useDebounce custom hook example

function useDebounce(value, delay) {
  const [debounced, setDebounced] = useState(value);

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

  return debounced;
}
135

useReducer + immer pattern (simpler immutable updates)

import produce from 'immer';

function reducer(state, action) {
  return produce(state, draft => {
    switch (action.type) {
      case 'updateName':
        draft.user.name = action.payload;
        break;
      // ...
    }
  });
}
136

What is the React Compiler (React Forget)? (2025 status)

Opt-in compiler (React 19+) that automatically memoizes components, hooks and values → reduces need for manual useMemo / useCallback / React.memo.

Still experimental / opt-in in most codebases as of early 2026.

137

What is useActionState? (React 19 forms)

Replaces manual pending/error/state management for form actions.

const [state, formAction, isPending] = useActionState(async (prev, formData) => {
  // server action logic
  return { success: true };
}, { success: false });
138

When should you NOT use useMemo / useCallback?

  • Trivial calculations
  • Primitives (numbers, booleans, short strings)
  • When parent doesn’t use React.memo
  • When cost of memoization > cost of re-creating value
  • Premature optimization
139

Common fetch + abort pattern with useEffect

useEffect(() => {
  const controller = new AbortController();
  fetch(url, { signal: controller.signal })
    .then(r => r.json())
    .then(setData)
    .catch(err => { if (!controller.signal.aborted) setError(err); });

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

Why custom hooks usually improve testability

  • Isolate logic from UI
  • Easy to test with renderHook
  • Can test pure state transitions
  • Reusable across many components
  • Clear input → output contract
141

Pattern: combining multiple contexts with one custom hook

function useAppContext() {
  const theme = useContext(ThemeContext);
  const user  = useContext(UserContext);
  const lang  = useContext(LanguageContext);
  return { theme, user, lang };
}
142

What is use() in React 19? (experimental)

Allows reading promises / context in render (with Suspense).

const data = use(fetchDataPromise);

Works like await but in render phase → triggers Suspense fallback.

143

Pattern: custom hook for handling click outside

function useClickOutside(ref, handler) {
  useEffect(() => {
    const listener = e => {
      if (!ref.current || ref.current.contains(e.target)) return;
      handler(e);
    };
    document.addEventListener('mousedown', listener);
    return () => document.removeEventListener('mousedown', listener);
  }, [ref, handler]);
}
144

useDeferredValue vs debounce – which to choose when?

Goal Recommended Reason
Reduce expensive renders on typing useDeferredValue React-aware, interruptible, no fixed delay
Reduce API calls on typing debounce Controls network timing
145

Best practice: naming & folder structure for custom hooks

  • Always start with use: useWindowSize, useAuth, useFetch
  • Put in src/hooks/ folder
  • One hook per file: useDebounce.js, useLocalStorage.js
  • Document params & return value with JSDoc
146

Why do we sometimes wrap setState in useCallback even if not passed to child?

When the setter is used inside useEffect and the effect depends on the setter → prevents unnecessary effect runs.

But in React 18+ most cases are unnecessary because setters are stable.

147

What happens when you return a promise from useEffect?

Nothing special — React ignores it.

Use async function inside effect + .then / await if needed.

Never return promise directly from useEffect.

148

Pattern: useReducer instead of useState + useEffect for form wizard

Centralizes step navigation, validation, data in one reducer → easier to reason about flow.

Actions: nextStep, prevStep, updateField, validate, submit

149

Common mistake: putting function definition inside useEffect deps

// BAD - runs every render
useEffect(() => {
  const handler = () => console.log(count);
  window.addEventListener('click', handler);
  return () => window.removeEventListener('click', handler);
}, [count, handler]);   // handler changes every render

Fix: wrap in useCallback or move inside effect.

150

2025–2026 React Hooks best-practice summary (top 8)

  1. Always use ESLint react-hooks plugin
  2. Prefer useEffect over useLayoutEffect unless flicker occurs
  3. Use useMemo / useCallback only when they provide measurable benefit
  4. Custom hooks for all reusable logic
  5. Clean up every subscription / timer / listener
  6. Prefer React Server Components + Server Actions when using Next.js
  7. Learn useTransition / useDeferredValue for responsive UIs
  8. Consider React Compiler (Forget) to reduce manual memoization