How does React's useEffect hook have access to all function expressions defined beneath it?

Viewed 729

I've created a functional component in React like this:

import React, {useEffect} from "react";
import "./styles.css";

export default function App() {

  useEffect(() => logSomething(), [])

  const logSomething = () => console.log('whats up');
  return (<div> hello </div>)
}

This function runs without any errors, and successfully logs out "whats up" in the console.

When I try the following, however, I get a TypeError:

export default function App() {

  useEffect(logSomething(), [])

  const logSomething = () => () => console.log('whats up');
  return (<div> hello </div>)
}

Similarly, I get a TypeError when I try this:

export default function App() {

  useEffect(logSomething, [])

  const logSomething = () => console.log('whats up');
  return (<div> hello </div>)
}

Why does the first example succeed, but the second and third example fail? Is the first function expression getting hoisted somehow? And if so, why aren't the other 2 function expressions being hoisted? Or is this just some quirk with how function references work in JavaScript?

2 Answers

In your second and third examples, you are attempting to use logSomething before it is defined. This results in a type error.

The first example works, because the anonymous function passed into useEffect isn't immediately called, and so it's okay that logSomething isn't defined yet. By the time that function is called, const logSomething has been defined and is in scope.

This behavior is due to the design of JavaScript itself, and has nothing to do with React or the useEffect hook.

Within a block, both let and const are hoisted, but are initialized only when the corresponding declaration statements are executed. The period between the hoisting and the initialization is commonly referred to as the 'temporal dead zone (TDZ)'. Attempting to access variables in the TDZ raises a ReferenceError.

In your second and third example, although logSomething is declared within the same block, you attempt to access them (Either by attempting to run the assigned function, or pass the assigned function's reference to useEffect) before the initialization statement is executed, and thus you get an error. In the first example however, logSomething is accessed within the function passed to useEffect and by the time the function is executed, logSomething is already initialized. Thus, it works.


TL; DR

The first example works, because:

  1. logSomething is declared within the same block scope as the function passed to useEffect, and is thus available within scope.
  2. By the time the effect runs, logSomething declaration statement has already been executed, and thus you do not get a ReferenceError when you try to access logSomething (Unlike in the other two cases)

Here's an excellent answer if you'd like to learn more about the TDZ.

Related