Hacker News new | past | comments | ask | show | jobs | submit login

Is there an explanation somewhere on react.dev covering how Hooks actually work under the hood? They're the most magical part of React, and everybody I know (including me) had a hard time actually grasping how they work and why, for example, you can't use a hook inside an if branch.

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.




I find this to be a very nice high-level explanation: https://medium.com/@ryardley/react-hooks-not-magic-just-arra...

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.


>In reality we use a linked list rather than an array

Why a LinkedList rather than an array?


> and why, for example, you can't use a hook inside an if branch

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.


The simplest version is:

- 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:

https://www.swyx.io/hooks


My high level understanding of how they work is that they remember the sequence in which they were called - so if you call the same hook three times, those three calls are held in a list - and future actions that should retrieve them get access to the correct data.

If you were to call them in an "if" branch it would mess up that queue mechanism.


I read the code when they added them. It may have changed, but here it is:

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.


Basically the way of thinking about is that there is a runtime that knows what component is rendering, and your hooks are communicating with that global runtime. This is why hook order and consistency matters - there is basically something globally that identifies a hook by its index order of execution and the identity of the component instance that is currently rendering.

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/


1. They work by setting a global variable to the value of the current component then calling the render function. Whenever you call a hook you're effectivelly dispatching it to the component in question, OOP style.

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.


I know almost nothing about React, let alone React hooks, but all this looks a little hack-etty, doesn't it?

Also, I'm curious if there is any similarity between these React hooks and the infamous Drupal hooks.


Its absolutely hacketty, yes.

Not familiar with Drupal.


It’s different but has similarity. Both are function that gets called when some lifecycle is reached.

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.


Here's how I'm pretty sure they work (although I haven't actually looked at the internals). Since your `<Tagname props>children</Tagname>` gets turned into `React.createElement(Tagname, props, children)`, this means your `Tagname` function isn't called directly. So before it calls that function, it sets up a "context" that the hooks will use during that function call (with a global variable pointing to that context so the hooks can find it). We could use an array with one element for each hook invocation. So each useState would use a different slot in that array, etc. This is also why the order and number of hooks must always be the same for a given function, since their identifier is their index in that array.

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.


Everytime I try to understand how something in react works I actually look into how it's implemented in preact. Hooks is around 500 LOC. https://github.com/preactjs/preact/blob/master/hooks/src/ind... React does basically the same, just in a more complex way (because fiber and native etc). But just giving you a mind model the preact implementation is enough.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: