score:8

Accepted answer

Your example works ...

setOrientation will actually change the state value of orientation and useDeviceDimensions will return the changed orientation. The issue is rather where you placed the console.log statement - and you are right, that is related to the closure (and its scope) passed to useEffect.

useEffect is invoked only once with your defined callback. This callback is a closure and can access orientation from useState. At the time of the closure creation, orientation variable will have the value null. Because there is never a new closure created ([] dependencies in useEffect), it will always print the value null, when the contained change listener is triggered.

However, setOrientation inside setDimensions will dispatch and communicate the change to React. The flow is like this:

  1. The useEffect closure is invoked once on first render.
  2. Sometime later a change event is triggered.
  3. setDimensions inside your closure always prints the same value from the function outer scope orientation variable (which at construction time has value null).
  4. setOrientation is invoked, React internally updates the state and triggers a new render cycle in the parent component.
  5. useDeviceDimensions is invoked again from the parent component, it now has and returns the new orientation state.
  6. A new change is triggered, go on with 3 again.

Point 4 works in the closure, because React has an internal list of “memory cells” for each component, where it can read and update the recent state, so updates work across the closure scope (see here for more info). The read side orientation is a stale variable, if you will, that just has a reference to the first state value, that React stored initially.

... but better declare useEffect in a safe way

Your useEffect hook uses state you don't mention in its dependencies array. What React docs say concerning this:

If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders. Link

So if you also want to use orientation in useEffect, you could declare it as dependency:

 useEffect(() => {
  ...
  }, [orientation]);

Have a look at this Codesandbox for an example, hope it clarifies things a bit!


Related Query

More Query from same tag