The React state management hooks, useState and useReducer, can accept initialization functions that are evaluated only during the first render. For instance, if one needs to create an initial value using a computation that is expensive, it can be done simply like this.
However, this approach calls veryExpensiveInitFunction() during every render, even though its result is only needed for the initial rendering.
To optimize this, you can pass the initialization function to useState and useReducer. This function will not be called until the hook is invoked and will only execute once when the component mounts. In the case of useReducer, the second argument initialArg is used as the initial value through the third argument init function.
useState and useReducer can be found in packages/react/src/ReactHooks.js.
Where does the crucial resolveDispatcher reside? It is also located in packages/react/src/ReactHooks.js.
In src/ReactCurrentDispatcher.js, we find ReactCurrentDispatcher.
It appears that ReactCurrentDispatcher.current is injected elsewhere, which happens in ReactFiberHooks.js.
We can infer that HooksDispatcherOnMount is used when there is neither a current state nor a memoized state, while HooksDispatcherOnUpdate is used otherwise.
Since we are curious about the behavior of the 'initialization function', letโs examine HooksDispatcherOnMount.
mountState calls mountStateImpl, where it checks whether initialState is a function, and if so, calls it to generate the initial value.
In the state update function, although not used, initialArg is passed as an argument. If an initialization function is passed, it is executed once at mount time to derive the value, otherwise, initialArg is used. If initialArg is not a function passed as an argument but the result of a function call (i.e., veryExpensiveInitFunction()), then this costly function will be called on every render.
For mountReducer, it generates the initial value based on the presence of the init argument.