“Hacking the Mainframe”: Digging into React Fiber with Nothing but the Debugger

Digging into a framework is as easy as hacking a mainframe in a movie. You can do it too!

Hack into the mainframe with me

I don’t like magic in my software. Don’t get me wrong — it can be fun to see a value miraculously change on screen when I update it in my JavaScript code, but I find it a bit unsettling when I can’t tell how it’s actually done. I also find that sometimes we treat those under-the-hood-internal-magic-things our libraries do as something beyond our comprehension. It becomes “that black-box those geniuses over there built”. But it doesn’t have to be.

I’ve spent the last few weeks digging around inside React’s code, trying to figure out how it does what it does. And while I was prepping for my talk with Uri Shaked about Angular Ivy and React Fiber, I had the chance to throw some debugger statements in the code and see what happens. Turns out you can get pretty deep inside a framework with nothing but the developer tools. In fact — you can understand almost everything about it.

So today I’m going to show you how I dug into React’s inner workings with just some debugger statements, and hopefully convince you that you can do it too. We’re going to focus on one question — what happens to the partial state we pass to ? Where does it go?

The idea is to treat this process as I would in real life, if I was trying to figure it out for the first time. With one main exception: I’ve done this before. Multiple times (so, so many times). And so I will take some shortcuts and add some background information, so that this post can be a reasonable length (because I love you!). But believe me when I tell you — I learned almost all of this from iterating over the process I show here, and some from external resources which I will link to. And so can you.

If you want to check out the app I work with in this post, and possibly throw in some debugger statements of your own, you can do so via the link below. Though fair warning — it is probably the least useful app I have ever written.

https://stackblitz.com/edit/react-example-blog

Setting Up

We are going to be following to see what happens with the partial state we pass to it. In order to do that, I’m going to do something you wouldn’t likely do in real-life: I’ll call from within a … in .

This is weird, I know

I’m doing this because the alternative way to trigger a state change without too much fuss would have been via an event handler. However, since React uses Event Delegation, this flow results in a slightly different call stack which will make it more difficult for us to see what’s going on. That’s why I’ve created this contrived example where is triggered externally. So our app begins with 🐿 ️, and about 1 second later the callback will run, and this will change to 🐸.

When I first started looking through React internals, the second most-common question I asked myself was “what do I do now?” (the first was of course “what the fuck?”). Over time I realized that I can always use something I already know, and build on top of it to go one step further. For instance, in this case we know that whenever we call , this eventually triggers a lifecycle method, the most obvious one being .

So I should be able to put a debugger statement in my method and see what happens in between those two points.

This is also weird, but bear with me, magic will happen

Now when the page reloads, the execution will pause at the debugger statement. By looking at the screen I can tell that the component has already been updated with the new state, and the DOM has already been re-rendered. A look inside the call stack can reveal the path React took to get from to here.

First Look at the Call Stack

If you check out our call stack now, it may look a bit daunting. That’s OK. It’s still just a call stack. Some of those functions might seem foreign, but we can also find some anchors we recognize — whether it’s and , or the later functions that have to do with lifecycles.

Spoiler alert: is where most of the magic happens — this is the place where React initiates it’s rendering phase that is described in Lin Clark’s wonderful Fiber talk. So if you really want to get your hands dirty you might consider setting a few breakpoints in that bad boy and seeing what happens.

I am so, so sorry…

We’re going to focus on following our state, and the first function that gets called after is . So let’s see what happens there:

enqueueSetState

Its arguments are , , and . A quick glimpse inside (the function which calls it) will show us that these are respectively the component instance, the partial state passed to and the callback passed to , if there is one.

Now the first thing this function does is retrieve the component’s Fiber. If you’re unfamiliar with Fibers, check out this great in-depth explanation: https://github.com/acdlite/react-fiber-architecture, but for our purposes, let’s use this mental model — a Fiber is an internal representation of a component that React uses to do its reconciliation. All Fibers are linked together in a structure called a linked-list-tree, which is designed to allow for easy and efficient traversal between Fibers that need to share data. Here, in , we can clearly see the relationship between the Fiber and the component instance.

Next, React sets the variables and , and we’re going to take React’s word for what it does there, and not delve into the implementation details. Then it creates a variable called with , and saves our on it. The argument stores our partial state, so we should definitely look into that. Let’s take a peek inside our object.

The first thing we can see here, is that the is in fact the partial state we had passed to . And we can see something else — that the expiration time for this update is not actually a time, or some big number representing a timestamp like we might expect, but 1. This is because React treats this as a synchronous update, which means it’s not going to do all that Fiber magic of stopping mid-work to yield control to the browser. I can’t tell you for sure why React does that — but my educated guess is it has to do with the fact this call came from within a lifecycle method, and React wants it to execute as soon as possible, without pausing.

We can elegantly ignore the next few lines, as they have to do with the parameter, which we didn’t pass a callback to our , I’m going to elegantly ignore those lines of code and move on. I’ve figured out that my partial state went into the object, so now I want to follow that and see what React does with it, and the next thing it does is call .

enqueueUpdate

If we follow this function back to where it’s defined, the first thing we can see is that React reads a property called off the Fiber. This function has already returned, so we could add a breakpoint here and rerun, but we still have access to our Fiber so another option is to print out the value of in the console. Compare and contrast the Fiber and the Alternate for a bit and you will find this — the is itself a Fiber that is a representation of the same component, with slight variations from our current Fiber. If you look more closely at the differences you might be able to spot this:

One of these states is not like the other

Keep in mind that at this point in time our component and view have already been updated, meaning that React has finished its reconciliation work and this is the final state of these two Fibers. Our takeaway from this, is one Fiber represents the previous state, and the other represents the current state.

Let’s look back to for a minute. What else does it do? Well, it seems to do some checks on the two Fibers, to see which of them exists, and also tests a specific property on the Fibers, the :

In fact, if we read through the function we’ll see that the , which includes our partial state, is appended to one or both of these queues. Since we have access to the final versions of these two Fibers, we can compare them and see what each looks like:

It seems the Alternate has nothing much in it’s queue, but it does have the final state saved in its property. In contrast, the Fiber we started out with has the previous state as its , but it also has a and , which look familiar…

Notice the `payload` property

Yep, that’s right, it’s our state update, saved as the first (and last) update in this Fiber’s update queue. If we fill in the blanks, we can infer that React must have saved the new state as part of the first Fiber’s , and then did something to process it — likely iterated over the queue — and created the final state in the Alternate.

And in fact, had we dug in a bit deeper and looked inside React’s reconciliation algorithm — specifically at its Render Phase and the function — we would have found exactly that. What’s more — we would have seen that React uses these Fibers alternately (a huge clue into that is that the Alternate’s property points to the original Fiber, so they are interchangeable). To be precise — at almost all times there are two identical Fiber trees in React, and it uses one to keep a reference to the old state, and one to create the new state.

React still has a lot more work to do with updating other Fibers, the DOM and so on… but for now we’ve managed to answer our big question. So what happens to the partial state? It gets saved to the Fiber’s queue, and is then used to update the state of the Fiber’s . While figuring this out — we’ve learned a couple of things about how React operates internally. And we did this with nothing but our developer tools and logic.

No magic necessary.

I leave you with this; you deserve it!

Senior FE developer at tikalk.com, Junior developer in my spare time, always a Lady with Opinions™. I write about things I learn. Find me on twitter @_bondit_

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store