Skip to content

Instantly share code, notes, and snippets.

@markerikson
Created September 14, 2019 00:31
Show Gist options
  • Save markerikson/0072527e810313dcf3767239cce66ce2 to your computer and use it in GitHub Desktop.
Save markerikson/0072527e810313dcf3767239cce66ce2 to your computer and use it in GitHub Desktop.
Reactiflux chat log: How does React store hooks on Fibers?

[12:14 AM] acreddy : are hooks value stored in fiber?

[10:40 AM] ghardin137 : not really

[10:50 AM] acemarke : @acreddy, @ghardin137 : yes they are, actually.

A "fiber" is a plain JS object that React uses to store bookkeeping information on each rendered component in the tree. The linked list of hooks is indeed stored as a field on the fiber for that component

10:50 AM] ghardin137 : isn't the fiber the specific update though?

[10:50 AM] ghardin137 : and that's actually setting the object outside the fiber?

[10:51 AM] acemarke : Fibers get copied during updates, but the whole render pass involves starting from the last tree of fibers

[10:51 AM] acemarke : Hang on

[10:51 AM] ghardin137 : i'll have to go back and look at the code again

[10:52 AM] ghardin137 : it looked like there was a reference in the fiber, but not actually part of it

[10:53 AM] ghardin137 : or potentially i'm not clear where the fiber starts in the code

[10:53 AM] acemarke : Here's the main hooks file:

https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js

[10:54 AM] ghardin137 : yeah that's the one i've read before

[10:54 AM] ghardin137 : it's possible that the fiber actually starts above where i think it does

[10:55 AM] acreddy : @acemarke so when there's a state update reconciler goes throught fiber and sees which has state updates and it updates state in fiber and marks as dirty nodes and carrys on. Next when the fiber is committed it applies the effects?

[10:56 AM] acreddy : On dirty nodes

[11:03 AM] ghardin137 : so i see they're saying that hooks are stored as a linked list on the fiber, but i'm not honestly sure where they're going through them. i see them doing the first hook, but none of the others.

[11:18 AM] acemarke : I haven't gone through the whole file, but there should be some kind of module global variable for the current hook or something

[11:34 AM] ghardin137 : yeah i saw the variable for that, i just never see it getting updated

[7:57 PM] acemarke : @ghardin137 , @acreddy : okay, lemme trace backwards through that file and recap as I go

[7:58 PM] acemarke : first, for reference, here's the object definition of what a Fiber looks like:

https://github.com/facebook/react/blob/45b6443c90d1db08a2ff223ec3fa0b2eafddcb67/packages/react-reconciler/src/ReactFiber.js#L125-L251

[7:59 PM] acemarke : in particular, it has a memoizedState: any field:

https://github.com/facebook/react/blob/45b6443c90d1db08a2ff223ec3fa0b2eafddcb67/packages/react-reconciler/src/ReactFiber.js#L181

[7:59 PM] acemarke : while the hooks all get attached in the same place, we'll go with useState as the starting point

[8:01 PM] acemarke : there's a few different versions of useState in the hooks file, for handling different scenarios: mount/update, dev vs prod, not a function component, etc

[8:01 PM] acemarke : basically, the hook functions are facades around the real logic

[8:02 PM] acemarke : here's the version of useState() that is called, on mount, in dev:

https://github.com/facebook/react/blob/45b6443c90d1db08a2ff223ec3fa0b2eafddcb67/packages/react-reconciler/src/ReactFiberHooks.js#L1418-L1426

[8:03 PM] acemarke : specifically, it calls this mountState() function:

https://github.com/facebook/react/blob/45b6443c90d1db08a2ff223ec3fa0b2eafddcb67/packages/react-reconciler/src/ReactFiberHooks.js#L1426

[8:05 PM] acemarke : mountState is defined on line 812:

https://github.com/facebook/react/blob/45b6443c90d1db08a2ff223ec3fa0b2eafddcb67/packages/react-reconciler/src/ReactFiberHooks.js#L812

[8:05 PM] acemarke : and starts out with:

  
  const hook = mountWorkInProgressHook();
  
  if (typeof initialState === 'function') {
  
    initialState = initialState();
  
  }
  
  hook.memoizedState = hook.baseState = initialState;
  

[8:06 PM] acemarke : mountWorkInProgressHook is on line 512:

https://github.com/facebook/react/blob/45b6443c90d1db08a2ff223ec3fa0b2eafddcb67/packages/react-reconciler/src/ReactFiberHooks.js#L561-L580

  
function mountWorkInProgressHook(): Hook {
  
  const hook: Hook = {
  
    memoizedState: null,
  

  
    baseState: null,
  
    queue: null,
  
    baseUpdate: null,
  

  
    next: null,
  
  };
  

  
  if (workInProgressHook === null) {
  
    // This is the first hook in the list
  
    firstWorkInProgressHook = workInProgressHook = hook;
  
  } else {
  
    // Append to the end of the list
  
    workInProgressHook = workInProgressHook.next = hook;
  
  }
  
  return workInProgressHook;
  
}
  

[8:07 PM] acemarke : so what's this workInProgressHook, and firstWorkInProgressHook stuff? they're not local to the function, so they have to be module-global variables

[8:07 PM] acemarke : aaaand here's what we were looking for:

https://github.com/facebook/react/blob/45b6443c90d1db08a2ff223ec3fa0b2eafddcb67/packages/react-reconciler/src/ReactFiberHooks.js#L165-L173

  
// Hooks are stored as a linked list on the fiber's memoizedState field. The
  
// current hook list is the list that belongs to the current fiber. The
  
// work-in-progress hook list is a new list that will be added to the
  
// work-in-progress fiber.
  
let currentHook: Hook | null = null;
  
let nextCurrentHook: Hook | null = null;
  
let firstWorkInProgressHook: Hook | null = null;
  
let workInProgressHook: Hook | null = null;
  
let nextWorkInProgressHook: Hook | null = null;
  

[8:08 PM] acemarke : so if hooks are a linked list, there has to be somewhere that the head of the list is assigned to the fiber, right?

[8:09 PM] acemarke : and there is, in a function called renderWithHooks(). Specifically, line 468:

https://github.com/facebook/react/blob/45b6443c90d1db08a2ff223ec3fa0b2eafddcb67/packages/react-reconciler/src/ReactFiberHooks.js#L468

[8:09 PM] acemarke : so there you go. Fiber objects have a field for storing arbitrary "memoized state". I assume that for class components, that's the actual value that becomes this.state. For function components, it's the pointer to the head of the hooks linked list

renderedWork.memoizedState = firstWorkInProgressHook;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment