Visualizing React hooks’ lazy initial state

Most examples of React hook’s lazy initial state uses inline arrow functions to showcase the usage:

function App() {
  const [state, setState] = React.useState(() => expensiveComputation());
  // ...
}

Paired with the idea that whatever you pass to React.useState is the initial value of the state, it can be hard to grasp the difference from the example below:

function App() {
  const [state, setState] = React.useState(expensiveComputation());
  // ...
}

For me, it helps to visualize the difference if you assign what’s inside the parentheses to a constant.

function App() {
  const initialState = 0;
  const [state, setState] = React.useState(initialState);
  // ...
}

Everytime App re-renders, the function App will re-run completely. This means 0 is set to initialState in every render. Now let’s do the same with the expensive computation example:

function App() {
  const initialState = expensiveComputation();
  const [state, setState] = React.useState(initialState);
  // ...
}

It’s pretty clear now that the expensive function is called every time the component renders. React.useState is just ignoring its result in subsequent renders. And that’s what you want to avoid when passing a function to the hook.

React.useState implementation detects if you’re passing a function and makes sure to call it once for the component’s lifetime.

The tradeoff now is that you’re creating a new function for every render. That’s acceptable if the computation takes longer or is more complex than instantiating an inline function. If that’s not the case (for example, when setting a constant like 0 in the first example), go with passing the value directly to React.useState.