Edit: there are a lot of good--and varied--explanations here. Which is why I think the docs should cover it in-depth. It's confusing.
Edit: there are a lot of good--and varied--explanations here. Which is why I think the docs should cover it in-depth. It's confusing.
In reality we use a linked list rather than an array. If you wanna dive into the code, I can give some pointers. For example, useState is implemented like this during first render (https://github.com/facebook/react/blob/87c803d1dad7e5fe88634...) and like this during next renders (https://github.com/facebook/react/blob/87c803d1dad7e5fe88634...).
However, _conceptually_ I'd recommend to think of Hook return values similar to "extra inputs" to your function, kind of like extra arguments. There are different ways to formalize it in different languages. We picked plain function calls for simplicity and low overhead, although you could imagine `yield` with generators or something like that.
Why a LinkedList rather than an array?
Within the component functions's body, you may have many calls to the same hook, lets take useState() as an example.
Each useState() returns a different state variable, that is kept track of in a cache outside the component function.
On the first render, the state variables are created. On subsequent re-renders, the state variables are read from the cache.
The cache is a simple array. It keeps track of, and identifies each individual state variable from it's index in the cache array.
The first call to useState() gets slot 0 in the array, the second call gets slot 1 and so on and so forth..
For the tracking to work consistently, all calls to useState() within the function's body must also happen consistently.
In the same order, every time. Having a useState call within a conditional "if" branch breaks that consistency.
- React has a module-scoped variable in the hooks implementation file that tracks which component is currently rendering
- The internal "Fiber" data structure that describes a component instance has a linked list of hook contents (saved values and callbacks)
- As each hook gets called, React tracks the current hook entry, looks up the current hook data, and returns it
So, the number of hooks used needs to be the same each time the component renders so that the linked list entries match up consistently.
There's a great talk by @swyx here that builds a miniature hooks implementation in about 30 minutes:
If you were to call them in an "if" branch it would mess up that queue mechanism.
1) A hook adds a function or variable to one of several lists attached to the component object, when it's instantiated (yes, even your "functional" components are objects, and I don't just mean in the JS-functions-are-actually-objects sense—at least, they were when I read it)
2) Subsequent calls either call those functions, or accesses/modifies the value, in a FIFO manner, reading them out of those lists. This is why you can't mess with hook ordering in e.g. loops.
It's basically just methods and properties on an object, but with FIFO access based on declaration order, instead of using a lookup table of some sort.
[EDIT] A poster correctly pointed out (then deleted their post) that I wrote "loops" where I meant "conditionals". Technically sorta-true (though not quite, as phrased) if the loop isn't the same length every time, but yeah, I meant conditionals. Point is, the reason order matters is that the whole thing's just a bunch of FIFO queues, more or less.
So there is a data structure that store says `[useMemo, useState, useEffeect]` - and when you component re-renders, is unmounting, or has effects to trigger it uses the index order to lookup the bit of state that needed to persist.
This is a pretty good high level explainer of how react works that touches on some of that: https://overreacted.io/react-as-a-ui-runtime/
2. React counts the number of executions of (certain) hooks. This count is how it knows which state to get from the store as a return value from `useState`. useState is effectively `getStateAndSetter()` but it doesn't pass a key name of any kind, so the implicitly passed key is `hookCount++`. This is why you can't call hooks conditionally, or state would get all messed up - if a condition turns false and one hook doesn't run that render, all getStateAndSetter calls that run after it will be off by one.
Also, I'm curious if there is any similarity between these React hooks and the infamous Drupal hooks.
Not familiar with Drupal.
React hooks is running on each component, not on whole app. React app is composed by bunch of components, and each of those have their own hooks.
Additionally, this context would also have the context of children elements, so things can actually be persisted across function calls and React can know what needs to be mounted & unmounted.
Also note that because Tagname isn't called directly, it's also how React is able to do its diff and only actually call what is needed.
This is also why if you're generating a dynamic number of elements (ie outputting an array of elements), you should provide a `key` prop, so it can link up the elements to their corresponding past states each time the function runs.