A component's hooks (from useState, useEffect, etc.) are stored as a
plain list attached to its fiber. Every render walks that list from the
start, in the SAME call order the hooks were declared in -- which is
exactly why hook calls can never be conditional: the mapping from "the
3rd use_state() call in this render" to "hook slot #2 in the list" only
holds if every render walks the cursor starting from 0.
Right now, a component's state resets or scrambles on its SECOND render onward -- the first render always looks correct, which is what makes this bug sneaky.
Your task: find the bug in ComponentInstance.start_render() and fix
it. use_state()'s cursor-advancing logic is already correct -- don't
change it.
Example that currently fails
instance = ComponentInstance()
instance.start_render()
_, count_index = instance.use_state(0)
instance.set_state(count_index, 5) # simulate calling the setter
instance.start_render()
count_value, count_index_2 = instance.use_state(0)
count_value # should be 5 (the persisted update)
count_index_2 # should be 0 (same slot as before)
# currently: start_render() only resets _cursor when self.hooks is EMPTY
# -- true for the very first render, false for every render after, so
# the cursor keeps climbing instead of restarting at 0, and use_state
# ends up reading/creating the WRONG slot on every render after the first.
Real-world grounding
- React's hooks dispatcher and the hook linked list attached to each
fiber:
packages/react-reconciler/src/ReactFiberHooks.js-- seerenderWithHooks, which resetsworkInProgressHook/the hook cursor to the start of the list at the BEGINNING of every render, not just the first one.
Real React's hook list is an actual linked list (each hook has a next
pointer) rather than an indexed array, and additionally re-validates hook
COUNT between renders in development mode (the "Rendered more hooks than
during the previous render" warning) -- this exercise implements the
cursor-reset mechanism whose absence is what that warning is guarding
against.