React Interview Questions 6-10 (Hooks, State, Effects, Refs, Custom Hooks)

Welcome to Part 2 of our React interview series. In the previous lesson, we covered the building blocks (Components, Props, State). Now, we enter the era of Hooks.

Introduced in React 16.8, Hooks revolutionized how we write React components. They allow functional components to have state and lifecycle features that were previously reserved for class components. If you are coming from Python, think of Hooks as special mixins or decorators that give your simple functions superpowers.

6. What are React Hooks and what are the "Rules of Hooks"?

Hooks are functions that let you "hook into" React state and lifecycle features from function components. They always start with the word use (e.g., useState, useEffect).

There are two strictly enforced rules you must mention in an interview:

  1. Only call Hooks at the Top Level: Do not call Hooks inside loops, conditions, or nested functions. React relies on the order of Hooks calls to keep track of state between renders.
  2. Only call Hooks from React Functions: Call them from React function components or custom Hooks. Do not call them from regular JavaScript functions.

Analogy: Imagine a coat check at a theater. You give them your coat, and they put it in slot #1. You give them your hat, and they put it in slot #2. When you come back, you must ask for your items in the same order. If you skip slot #1 because of an "if statement," the attendant will give you your hat when you asked for your coat. The order matters!

// ❌ BAD: Conditional Hook
if (userIsLoggedIn) {
  const [name, setName] = useState("User"); // React loses track if this condition changes!
}

// ✅ GOOD: Top Level
const [name, setName] = useState("User");

// Use the condition inside the logic, not to define the hook
useEffect(() => {
  if (userIsLoggedIn) { /* logic */ }
}, [userIsLoggedIn]);

7. Explain useState vs useReducer. When would you use one over the other?

Both hooks manage state, but they are designed for different levels of complexity.

  • useState: Best for simple, independent pieces of state (strings, booleans, numbers). It is direct and easy to read.
  • useReducer: Best for complex state logic where the next state depends on the previous one, or when multiple sub-values typically change together. It follows the Redux pattern (dispatching actions).

Analogy:
useState: A light switch. You just flip it On or Off. Simple.
useReducer: An airport traffic controller. You don't just say "move plane." You send specific commands ("Takeoff", "Land", "Taxi") and the controller updates the complex map of the runway accordingly.

// 1. useState (Simple)
const [count, setCount] = useState(0);
// setCount(count + 1);

// 2. useReducer (Complex/Structured)
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 };
    default: throw new Error();
  }
}

const [state, dispatch] = useReducer(reducer, initialState);
// dispatch({ type: 'increment' });

8. How does useEffect work? How do you replicate lifecycle methods?

useEffect lets you perform side effects (data fetching, subscriptions, manual DOM changes) in function components. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

Lifecycle GoaluseEffect SyntaxTrigger
MountuseEffect(fn, [])Runs once on load
UpdateuseEffect(fn, [prop])Runs when `prop` changes
UnmountuseEffect(() => cleanupFn, [])Runs cleanup on removal
useEffect(() => {
  console.log("1. Component Mounted (Fetching Data...)");

  // Optional: Return a cleanup function (Unmount)
  return () => {
    console.log("3. Component Unmounted (Cleaning up...)");
  };
}, []); // Empty dependency array = Run Once

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

useEffect(() => {
  console.log("2. Count Updated:", count);
}, [count]); // Runs whenever 'count' changes

Sample behavior (Console):

// On Load:
1. Component Mounted (Fetching Data...)
2. Count Updated: 0

// On Button Click (setCount):
2. Count Updated: 1

// On Page Leave:
3. Component Unmounted (Cleaning up...)

9. What is the difference between useRef and useState?

Both can store data, but they affect the React render cycle differently.

  • useState: Updating state triggers a re-render. Use this when the data affects what the user sees on the screen (UI).
  • useRef: Updating a ref does NOT trigger a re-render. The value persists between renders. Use this for mutable values that don't impact the UI (like timers) or accessing DOM elements directly.

Analogy:
useState: A public announcement system. When you change the message, everyone stops and listens (Re-render).
useRef: A sticky note in your pocket. You can write on it and read it later, but nobody else knows you changed it, and it doesn't interrupt the show.

const [count, setCount] = useState(0); // Changing this updates the screen
const renderCount = useRef(0);         // Changing this happens silently

useEffect(() => {
  renderCount.current = renderCount.current + 1;
});

return (
  <div>
    <h1>State: {count}</h1>
    {/* This will show how many times the component rendered, 
        without causing an infinite loop of renders itself. */}
    <h2>Renders: {renderCount.current}</h2>
    <button onClick={() => setCount(c => c + 1)}>Update State</button>
  </div>
);

10. What are Custom Hooks and why are they useful?

A Custom Hook is simply a JavaScript function that starts with "use" and calls other Hooks inside it. It is the primary mechanism for reusing stateful logic across different components.

Before Hooks, we used complex patterns like Higher-Order Components (HOCs) or Render Props to share logic. Custom Hooks make this much cleaner. If two components need to fetch data, toggle a modal, or track window size, you extract that logic into a Custom Hook.

// --- The Custom Hook (useToggle.js) ---
function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  
  const toggle = () => {
    setValue((prev) => !prev);
  };

  // Return exactly what the component needs
  return [value, toggle];
}

// --- Component A ---
const Modal = () => {
  const [isOpen, toggleModal] = useToggle(false);
  return <button onClick={toggleModal}>{isOpen ? "Close" : "Open"} Modal</button>;
};

// --- Component B ---
const Menu = () => {
  const [isExpanded, toggleMenu] = useToggle(true);
  return <button onClick={toggleMenu}>{isExpanded ? "Collapse" : "Expand"} Menu</button>;
};

Tip: Always name your custom hooks starting with use (e.g., useFetch, useForm). This tells React (and your teammates) that this function follows the Rules of Hooks.

You have now mastered the core toolset of modern React! Understanding the nuance between useEffect dependency arrays and knowing when to reach for useRef versus useState will solve 90% of the bugs you encounter. In the next lesson, we will tackle State Management and how to pass data around effectively.

🚀 Deep Dive With AI Scholar