React Hooks and Fiber deep diving
Tags: react, javascript, tutorial, hooks
Published: 2023-10-28 12:59:14

Starting from a code example that had a particular bug will be explored the source code of react focusing especially on hooks and fiber nodes.

## Introduction

The idea of this article is to navigate the source code of React to understand the logic behind the “magic” of hooks and fibers. The goal is to ( begin to) understand “where is what” in order to understand what React looks like especially in functionality related to hooks and virtual dom.

When i started to explore the source code, I gave myself a goal.

### The goal

One of the hooks I created was the *useEnhancedReducer*. In a nutshell, a starting point to enhance the React *useReducer*. **Since *useReducer* is available in React from version 16 I built it using this version** (Why limit a functionality only in newer version when older are also compatible?).

If you are interested in what the *useEnhancedReducer* is I, together with the company where I work: [MVLabs](https://mvlabs.it/), made a talk on it on [ReactJs Day 2023](https://2023.reactjsday.it/schedule/#HowToTrainYourUseReducerTurningYourStateManagementStrategyIntoANightFury).

In one of its early versions this hook had a bug. The bug was that **every time an action is dispatched the reducer code were called twice** (React 16 has no Strict Mode). The fix was fairly immediate but why there was this problem?

**So a great prompt for exploring React’s code is to find the lines that led to this problem.**

*If you have any doubts about how to test the code in, please see Appendix A.*

### Let’s begin!

Looking the [react source code](https://github.com/facebook/react) we can see that the repository is diveded into countless packages. The most interesting ones are: *react*, *react-reconciler*, *react-dom*, *shared*.

**To begin** with, I have taken the simplest hook: the *useState* (but you will see we are not far from the *useReducer* and hence the *useEnhancedReducer*) with the simplest possible code:
```javascript
    function MyReactComponentExample() {
      const [state, setState] = useState(0)
    
      const increment = () => {
        setState((prev) => prev + 1)
      }
    
      return ...
    }
```
**Before executing the code, let us first look statically at the react code**. Searching “hooks” inside *react* package we find the [ReactHooks.js](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js#L96) file and the following code.

```javascript
    export function useState<S>(initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {
      const dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
    }
```

The other hooks are very similar, all call *resolveDispatcher* and *resolveDispatcher* return *ReactCurrentDispatcher.current*.

```javascript
    export function useReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      const dispatcher = resolveDispatcher();
      return dispatcher.useReducer(reducer, initialArg, init);
    }
    
    function resolveDispatcher() {
      const dispatcher = ReactCurrentDispatcher.current;
      invariant(...);
      return dispatcher;
    }
```

But **where is assigned ReactCurrentDispatcher**?

This can be discovered “easly” setting a breakpoint in
```js
    setState((prev) => prev + 1)
```

and navigating the source code. At the first “step into”, the code will immediately enter inside the *react-dom.development.js* file. Looking for the same lines of code in the source code, however, we will see that this is written inside the *react-reconciler* package. Looking on *react* package there isn’t a reference on *react-dom* or *react-reconciler* (excluding tests). How can this be possible?

**Here is where *shared* comes into plays**. There is (almost) no coupling between packages. A package share an object with the *shared* package and other package can use (fill) objects taking them from *shared* so we know how react can deal with those two. On the other hand for the link between *react-dom* and *rect-reconciler* a good article that explain that is: [custom react render](https://agent-hunt.medium.com/hello-world-custom-react-renderer-9a95b7cd04bc) (In short, *react-dom* “implements” a *react-reconciler* configuration but also take a look at the article!).

### *react-reconciler*

Exploring the file [ReactFiberHooks.js](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js) inside *react-reconciler* we might notice the *Dispatcher* definition (the same type of *ReactCurrentDispatcher.current* we just encountered) and the objects that implement it. There are many of them, at least one for each state in the lifecycle of a React component!

```js
    export type Dispatcher = {
      ...
      useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],
      ...
    };
    
    ...
    
    const HooksDispatcherOnMount: Dispatcher = {
      readContext,
      ...
      useState: mountState,
      ...
    };
    
    const HooksDispatcherOnUpdate: Dispatcher = {
      readContext,
      ...
      useState: updateState,
      ...
    };
```

**To recap:** React uses *ReactCurrentDispatcher.current* which is shared with *react-reconciler* using *shared* package. *ReactCurrentDispatcher.current* is assigned with a different object based on the state of the react component.

### Why inside react-reconciler?

**React kept in memory a representation of a UI: The virtual Dom**. [From React docs](https://legacy.reactjs.org/docs/faq-internals.html):
>  The virtual DOM (VDOM) is a programming concept where an ideal, or “virtual”, representation of a UI is kept in memory and synced with the “real” DOM by a library such as ReactDOM. This process is called **reconciliation**.
>  …
>  React, however, also uses internal objects called “fibers” to hold additional information about the component tree. They may also be considered a part of “virtual DOM” implementation in React.

What are the *additional information about the component*? **Hooks** (and more)!

### Back to code

Back to code, the first “step into” of the breakpoint put on the line

```js
    setState((prev) => prev + 1)
```

had stopped the code within the function [dispatchAction](https://github.com/facebook/react/blob/v16.14.0/packages/react-reconciler/src/ReactFiberHooks.new.js#L1686).

```js
    function dispatchAction(fiber /* MyReactComponentExample */, queue /* [] */, action /* prev => prev + 1*/) {
      ...
    }
```

Let us analyse the *dispatchAction* code on the simplest case, thus with *queue* parameter empty.

- An *update* is defined and action ((prev) => prev + 1) is inserted inside.

```js
    const update: Update<S, A> = {
        ...,
        action, // (prev) => prev + 1
        eagerReducer: null,
        eagerState: null,
        next: (null: any),
      };
```

A few lines below, since queue is empty, the next state is computed **eagerly**.

- The previous state is taken and **the action is called**:

```js
    const currentState: S = (queue.lastRenderedState: any); // 0
    const eagerState = lastRenderedReducer(currentState, action); // ((0) => 0 +1 )
    
    // updating references
    update.eagerReducer = lastRenderedReducer;
    update.eagerState = eagerState;
```

If the new status is the same as the previous one there is no need to scheduling an update. Else, is necessary to schedule an update which can lead to a re-render and commit phase (this process is not covered in this article).

```js
    if (is(eagerState /* 1 */, currentState /* 0 */)) {
      ...
      return;
    }
    ...
    
    scheduleUpdateOnFiber(root, fiber, lane);
```

Ps. guess where *is* function is defined? shared of course!

### How is an hook hooked to a fiber?

Or “How is *setState(0)* hooked to *MyReactComponentExample?”*

We have seen how a *setState* is executed but how is the *useState* connected to a fiber?

Put a breakpoint on the instruction

```js
    const [state,setState] = useState(0)
```

On the first “step into” the code will reach the [mountState](https://github.com/facebook/react/blob/97047a810cb19e3041a3ec163f4fde85f495bdc1/packages/react-reconciler/src/ReactFiberHooks.js#L1751) *function*.*

```js
    function mountState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      const hook = mountStateImpl(initialState); // see below impl.
      const queue = hook.queue;
      const dispatch: Dispatch<BasicStateAction<S>> = (dispatchSetState.bind(
        null,
        currentlyRenderingFiber,
        queue,
      ): any);
      queue.dispatch = dispatch;
      return [hook.memoizedState, dispatch];
    }
    
    // React 18, in React 16 was a single function
    function mountStateImpl<S>(initialState: (() => S) | S): Hook {
      const hook = mountWorkInProgressHook(); // see below impl.
      ...
      hook.memoizedState = hook.baseState = initialState;
      const queue: UpdateQueue<S, BasicStateAction<S>> = {
        pending: null,
        lanes: NoLanes,
        dispatch: null,
        lastRenderedReducer: basicStateReducer,
        lastRenderedState: (initialState: any),
      };
      hook.queue = queue;
      return hook;
    }
    
    function mountWorkInProgressHook(): Hook {
      const hook: Hook = {
        memoizedState: null,
    
        baseState: null,
        baseQueue: null,
        queue: null,
    
        next: null,
      };
    
      if (workInProgressHook === null) {
        // This is the first hook in the list
        currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
      } else {
        // Append to the end of the list
        workInProgressHook = workInProgressHook.next = hook;
      }
      return workInProgressHook;
    }
```

**Thus**:

 1. A fiber stores an hook within the *memoizedState* key.

 2. An hook has a reference to the next hook (*next* key).

```js
    if (workInProgressHook === null) {
        currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
    } else {
        workInProgressHook = workInProgressHook.next = hook;
    }
```

Each hook has a queue where it contains the pending action (*pending*), the currently rendered state (*lastRenderedState*) and more data.

```js
    const queue: UpdateQueue<S, BasicStateAction<S>> = {
      pending: null,
      lastRenderedState: (initialState: any),
      ...
    };
```

### Back to setState

Now that we have seen how an hook is initialized and hooked to a fiber, we can return to the execution of the instruction *increment.*

```js
    const increment = () => {
        setState((prev) => prev + 1)
    }
```

The fiber will look like:

```js
    fiber: FiberNode {
      child: ...
      memoizedState: {
        ...
        next: null,
        queue: {
          ...
          lastRenderedState: 0,
          pending: {
            action: prev => prev + 1,
            ...
          }
        }
      }
    }
```

What if the code was this instead

```js
    ...
    const [state,setState] = useState(0)
    const [secondState,setSecondState] = useState("0")
    
    const increment = () => {
        setState((prev) => prev + 1)
        setSecondState((prev) => prev + "2")
    }
```

Inside the *dispatchAction* of *setState((prev) => prev + 1)*:

```js
    fiber: FiberNode {
      child: ...
      memoizedState: { // "A fiber stores an hook within the memoizedState key"
        ...
        next: { // "An hook has a reference to the next hook (next key)"
          ...,
          lastRenderedState: "0",
          pending: null,
        },
        queue: { // now the queue is no more empty
          ...
          lastRenderedState: 0,
          pending: {
            action: prev => prev + 1,
            ...
          }
        }
      }
    }
```

Inside the *dispatchAction* of setSecondState *((prev) => prev + “2”)*:

```js
    fiber: FiberNode {
      child: ...
      memoizedState: {
        ...
        next: {
          ...,
          lastRenderedState: "0",
          pending: { // setSecondState((prev) => prev + "2") in pending
            action: prev => prev + "2",
            ...
          }
        },
        queue: {
          ...
          lastRenderedState: 0,
          pending: {
            action: prev => prev + 1,
            eagerState: 1, // first hook eager calculatio 
            ...
          }
        }
      }
    }
```

The fiber contains the list of all hooks in order of definition and inside *dispatchAction* of *setSecondState* we have the *eagerState* of the first setState and the action of *setSecondState* in pending.

As we can see this function is not only called by the *useState* but also by the *useReducer*. In fact, the *useState* is nothing more than a simplified *useReducer*!
Ps. As of React 18 the function “dispatchAction” is no longer shared between useState and useReducer but they also remain very similar in creation and update as we will see later on this article.

### Where is the status actually calculated?

Early we saw just the eager computation it is now time to see how the actual computation works.

On the next render, re-executing the function *const [state,setState] = useState(0)* the function that will be called is [updateState](https://github.com/facebook/react/blob/v16.14.0/packages/react-reconciler/src/ReactFiberHooks.new.js#L1151) ( *updateState* calls *updateReducer* so *useState* is just a simplified *useReducer*). In this function all updates are calculated and the *newState* is determined. In this way after a setState the *state* have the value updated.

```js
    function updateReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      const hook = updateWorkInProgressHook();
      ...
    
      if (baseQueue !== null) {
        const first = baseQueue.next;
        let newState = current.baseState;
    
        let newBaseState = null;
        let newBaseQueueFirst = null;
        let newBaseQueueLast = null;
        let update = first;
        do { // for every update
          const updateLane = update.lane;
          if (!isSubsetOfLanes(renderLanes, updateLane)) {
           ...
          } else {
            ...
            // ---> Process this update <---
            if (update.eagerReducer === reducer) {
              newState = ((update.eagerState: any): S);
            } else {
              const action = update.action;
              newState = reducer(newState, action); // (prev) => prev + 1
            }
            // -----------------------------
          }
          update = update.next;
        } while (update !== null && update !== first);
    
       
        if (!is(newState, hook.memoizedState)) {
          markWorkInProgressReceivedUpdate();
        }
    
        ...
      }
    
      const dispatch: Dispatch<A> = (queue.dispatch: any);
      return [hook.memoizedState, dispatch];
    }
```

Now we have all the elements to understand **why the dispatched action call the reducer function twice**. The *useReducer* inside *dispatchAction* immediately calculates the next value (executing the reducer function) and updates *update.eagerReducer* and *update.eagerState* with the new values. In the first version of the *useEnhancedReducer* **at the re-render the reducer function was not cached (for example with a useCallback ) so is always recreated at every re-render and even if the function is the same the pointer differs from the previous**.

So inside the *updateReducer* function the reducer will be called a second time.

```js
    if (update.eagerReducer === reducer) { // --> false 
        newState = ((update.eagerState: any): S);
    } else {
        const action = update.action;
        newState = reducer(newState, action); // --> executing for the second time
        // the first time was inside dispatchAction on eager computation
    }
```

Ps. in React 18 this behavior has changed, the *useReducer* no longer pre-computes the new state (no more eagerReducer). Moreover bugs due to non-pure functions like this will be highlighted by strict mode.

(The pull request that removes the new state pre-computation, together with an explanation, can be found here: [22445](https://github.com/facebook/react/pull/22445))

## Conclusion

In conclusion, starting with the goal of understanding why the *useEnhancedReducer* was called twice we discovered:
- How *react*, *shared*, *react-dom*, and *react-reconciler* communicate;
- How hooks are defined, hooked to fibers, and what the *useState* and *useReducer* looks like;
- How hooks execute actions and update their state.

A schematic of the analyzed code is shown next

![recap](https://cdn-images-1.medium.com/max/2182/1*hBwz6MVz2YyVMsneHq7RWA.png)

I hope this article has shown something new, maybe replacing what seem like “the magic of react” with real code and that will make it easier for you to understand and explore react.

**If you enjoyed this article and want more content on React, you might be interested in the useEnhancedReducer, shown with [MVLabs](https://mvlabs.it/) at [ReactJs Day 2023](https://2023.reactjsday.it/schedule/#HowToTrainYourUseReducerTurningYourStateManagementStrategyIntoANightFury).**

### Appendix A

### How the source code can be debugged?

A method that i used to quickly write code, test it and debug source code is to:

1. Create a [CodeSandbox](https://codesandbox.io/);

2. Open the developer console onto the *source* tab (left circle in the image);

3. Discover the source code (if you will use CodeSandbox the source code path will be similar to the one in the picture), add a breakpoint on code and step into the source.

![— left-center, circled in blue, where breakpoint are added — right, circled in blue, all the breakpoints added and buttons to navigate the callStack.](https://cdn-images-1.medium.com/max/5118/1*RPLeIi_bYHn_mBIbAX9afA.jpeg)