hoisting in react

Intermediate

Hoisting in React: Intermediate Study Guide

Prepared by a Senior Software Engineer


Core Concepts

1. What is Hoisting?

Hoisting is a JavaScript mechanism where variable and function declarations are moved to the top of their containing scope (global or local) during the compilation phase. React does not redefine hoisting rules—it inherits them from JavaScript. However, React’s component model (functions, classes, hooks) creates unique scenarios where hoisting pitfalls commonly arise.

Key JavaScript Hoisting Rules:

Declaration TypeHoisting BehaviorExample
varFully hoisted. Declared variables are initialized with undefined.@l2: var x = 10; console.log(x); // undefined
let/constNot hoisted. Variables exist in a Temporal Deployment Zone (TDZ) until declared. Access before declaration throws ReferenceError.@l2: console.log(y); let y = 10; // ReferenceError
Function DeclarationFully hoisted. Can be called before declaration.@l2: sayHello(); function sayHello() { … }
Function ExpressionNot hoisted. Must be declared/assigned before invocation.@l2: sayBye(); const sayBye = () => { … }; // TypeError

🔍 React Context: These rules apply to component bodies, event handlers, and custom hooks. Misunderstanding hoisting leads to bugs in rendering logic, event callbacks, and state updates.


2. Hoisting in React Components

A. Functional Components

function MyComponent() {
  // ❌ Pitfall: `logVar` uses a variable declared later (hoisting issue)
  console.log(varA); // undefined (var is hoisted)
  const varA = 10;   // const is NOT hoisted → ReferenceError if accessed earlier

  // ✅ Safe pattern: Declare all variables BEFORE using them
  const varB = 20;
  console.log(varB); // 20
  return <div>{varB}</div>;
}

B. Loops & Event Handlers (Common Interview Scenario)

function ListItem() {
  const items = [{ id: 1 }, { id: 2 }];

  return (
    <ul>
      {items.map(item => (
        // ❌ BUG: `handleClick` is redefined on EVERY render ↘
        // ❌ Also: `var` hoisting causes ALL handlers to reference the LAST `item` ↘
        <li key={item.id}>
          <button onClick={() => console.log(item.id)}>
            {item.id}
          </button>
        </li>
      ))}
    </ul>
  );
}

Why This Fails:

  • var is hoisted inside the loop block, but not block-scoped. All onClick callbacks close over the same item (last iteration value).
  • Fix: Use const (block-scoped) or extract the handler inside the map callback.

C. Hooks (useEffect, useCallback) & Hoisting

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

  // ❌ BUG: `count` inside useEffect closes over the **initial** value (0) ↘
  useEffect(() => {
    console.log(count); // Always logs 0 (stale closure)
  }, []); 

  // ✅ Solution: Include `count` in dependencies OR use functional update
  useEffect(() => {
    console.log(count); // Updates correctly
  }, [count]);
}

Explanation:

  • useEffect callbacks are ** closures ** over variables in the render scope. Hoisting doesn’t affect this, but stale closures (due to missing dependencies) are a relatedpitfall.

3. Class Components & Method Hoisting

In class components, method declarations are automatically hoisted (unlike function expressions in functional components).

class MyClass extends React.Component {
  // ✅ Methods are hoisted—can be called in `constructor`
  constructor(props) {
    super(props);
    this.handleClick(); // Works!
  }

  handleClick() {
    console.log("Clicked!");
  }
}

⚠️ Caution: If you rewrite the method as a property initializer (arrow function), it won’t be hoisted:

handleClick = () => { … }; // NOT hoisted → cannot be used in constructor

Common Interview Questions & Ideal Answers

Q1. Why does this code log undefined? How would you fix it?

function Component() {
  console.log(msg); // undefined
  var msg = "Hello";
  return <div>{msg}</div>;
}

Answer:

  • Why? var is fully hoisted and initialized to undefined.
  • Fix: Use let/const and declare variables before use:
const Component = () => {
  const msg = "Hello"; // Declared first
  console.log(msg);    // "Hello"
  return <div>{msg}</div>;
};

Q2. Explain the bug in this component and how to resolve it.

function TodoList() {
  const [todos, setTodos] = useState([]);
  
  todos.map(todo => (
    <button onClick={() => setTodos([...todos, todo])}>
      Add {todo.text}
    </button>
  ));
}

Answer:

  • Bug: The todos array inside the map callback is stale—it captures the initial empty array due to missing dependencies. Hoisting isn’t the direct cause, but stale closures arise from improper hook usage.
  • Fix: Use useEffect or move logic into dependencies:
function TodoList() {
  const [todos, setTodos] = useState([]);

  useEffect(() => {
    // Logic using LATEST todos
  }, [todos]);

  return (
    <>
      {todos.map(todo => (
        <button 
          key={todo.id}
          onClick={() => setTodos(prev => [...prev, todo])}
        >
          Add {todo.text}
        </button>
      ))}
    </>
  );
}

Q3. When should you worry about hoisting in React?

Ideal Answer:

Hoisting matters most in these scenarios:

  1. Variable declarations inside loops (e.g., for, map) where var causes unintended sharing of values across iterations.
  2. Using var in component logic—prefer const/let to avoid undefined initializations.
  3. Class component constructors when calling methods: Only method declarations (not property initializers) are hoisted.
  4. Closures in hooks (useEffect, useCallback)—hoisted variables may cause stale enclosures if dependencies are missing.

Real-World Examples

Example 1: Inline Styles with Hoisted Variables

function Button() {
  var style = { color: "red" };
  console.log(style); // undefined (var hoisted)
  style = { color: "blue" }; // This overrides the declaration
  return <button style={style}>Click</button>;
}

Fix: Use const or declare before usage.

Example 2: Event Handlers in a List

function ColorPicker() {
  const colors = ["red", "green", "blue"];
  
  return (
    <div>
      {colors.map(color => (
        <button 
          key={color}
          onClick={() => console.log("Selected:", color)} // ✅ Safe: `color` is block-scoped
        >
          {color}
        </button>
      ))}
    </div>
  );
}

Study Tips

  1. Master JavaScript Scopes First: Understand var vs let/const, TDZ, and closure behavior.
  2. Practice Common Pitfalls: Write code with loops, event handlers, and hooks—intentionaly introduce hoisting bugs and debug them.
  3. Use Linters: Tools like ESLint (no-var, no-undef) catch hoisting misuse.
  4. Focus on Hooks: Analyze stale closures via React DevTools—hoisting isn’t the root cause, but it often co-occurs with closure issues.
  5. Review React Docs: Study how component state and hooks capture variables from renders.

Next Steps: Ready to dive deeper? Ask for a mock interview on hoisting scenarios, or request code walkthroughs for specific patterns!

Ready for a new challenge?

Start a new session to explore different topics or increase the difficulty level.

Start New Session
hoisting in react Preparation Guide | Interview Preparation