If you check the implementation on React github you notice that useState
is just calling useReducer
with a basic reducer:
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return useReducer(
basicStateReducer,
(initialState: any),
);
}
So looking for the useReducer
implementation, we see that the setter function is the dispatch
function which changing depending on the lifecycle we currently in
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
...
// dispatch depends on lifecycle
return [..., dispatch];
}
}
You can see the full useReducer
implementation here.
For detailed implementation, you should try and Build your own React which eventually will lead to a simplified version of this hook:
function useState(initial) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex];
const hook = {
state: oldHook ? oldHook.state : initial,
queue: []
};
const actions = oldHook ? oldHook.queue : [];
actions.forEach(action => {
hook.state = action(hook.state);
});
const setState = action => {
hook.queue.push(action);
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot
};
nextUnitOfWork = wipRoot;
deletions = [];
};
wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState];
}
In simple words: every hook is saved in "React hooks array" (that's why the call order is essential because hooks saved in array's indexes - see Rules of Hooks), and depending on the hook's index, whenever called it mutates the state object associated with the current component.