Understanding The React Source Code — UI Updating (Individual DOM) VIII

Photo by Cristina Gottardi on Unsplash

UI updating, in its essential, is data change. React offers a straightforward and intuitive way to program front-end Apps as most moving parts are converged in the form of states, and most UI updating can be conducted with a single

…, I mean, a single method, setState(). In this article, we are going unfold the setState() implementation, and peek inside the diffing algorithm by mutating an individual DOM element.

Before we get started, I would like to respond to one common feedback from readers: “why 15.x, why not fiber?”Well, simply put, because synchronous rendering is still alive. Thus, the code base (a.k.a., stack reconciler) specifically designed for synchronous rendering, in my opinion, offers an easier albeit solid ground to establish an initial understanding.

Let’s get started with an example extended from {post four}

class App extends Component {  constructor(props) {    super(props);    this.state = {      desc: 'start',      color: 'blue'    };
    this.timer = setTimeout(      () => this.tick(),      5000    );  }
  tick() {    this.setState({      desc: 'end',      color: 'green'    });  }
  render() {    return (      <div className="App">        <div className="App-header">          <img src="main.jpg" className="App-logo" alt="logo" />          <h1> "Welcom to React" </h1>        </div>        <p className="App-intro" style={{color: this.state.color}}>          { this.state.desc }        </p>      </div>    );  }}
export default App;

Compared to the App component used in {post four}, the new version adds style prop to <p> node, and setState()s desc to ‘end’ and color to ‘green’ 5 seconds after the component is constructed.

Before transactions

Firstly, the instantiating of App has been discussed in {post four}.

ctl-f “setState”In the same article, I also mentioned ReactInstanceMap, a back link (from the external ReactComponent instance) to the internal ReactCompositeComponent[ins], which will be used very soon.

Here I paste the data structure as a reminder.


Next we look at the setState() method body:

ReactComponent.prototype.setState = function (  partialState,  callback) {  // scr: ---> sanity check  this.updater.enqueueSetState(this, partialState);  if (callback) {  // scr: ---> no callbak  }};
[email protected]/modern/class/ReactBaseClasses.js

Yes, setState() is inherited from ReactComponent.

But wait, what is this.updater? isn’t it set to ReactNoopUpdateQueue in the constructor, and is a no-op? In fact, I believe with the understanding of Transaction(s) and instance pooling {last post}, if you trace back from the aforementioned ReactComponent instantiating {post four}, you will be able to find out the origin of this.updater very easily.

I will leave this question open so we can move faster to the core part —virtual DOM and diffing algorithm

enqueueSetState: function (publicInstance, partialState) {// scr: DEV code
  // scr: ------------------------------------------------------> 1)  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
  if (!internalInstance) {    return;  }
  // scr: ------------------------------------------------------> 2)  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);    queue.push(partialState);
  // scr: ------------------------------------------------------> 3)  enqueueUpdate(internalInstance);},
[email protected]/shared/stack/reconciler/ReactUpdateQueue.js

1) this is the method that obtains the internal ReactCompositeComponent[ins] from the back link ReactInstanceMap;

function getInternalInstanceReadyForUpdate(  publicInstance,  callerName) {  var internalInstance = ReactInstanceMap.get(publicInstance);
... // scr: DEV code
  return internalInstance;}
[email protected]/shared/stack/reconciler/ReactUpdateQueue.js

2) attach an array (_pendingStateQueue) to ReactCompositeComponent[ins], and push the changed state {desc:’end’,color:’green’} into it;

3) Starts the Transaction(s) {post six, seven} with one line method calling:

...function enqueueUpdate(internalInstance) {  ReactUpdates.enqueueUpdate(internalInstance);}...
[email protected]/shared/stack/reconciler/ReactUpdateQueue.js

The call stack so far:

|-ReactComponent.setState()  |-ReactUpdateQueue.enqueueSetState()    |-getInternalInstanceReadyForUpdate()    |-enqueueUpdate()      |-ReactUpdates.enqueueUpdate()        |~~~

Here I also paste the transaction related call graph as a reminder.


In transactions

The first stop after the Transaction(s) are fully initialized is:

function runBatchedUpdates(transaction) {  var len = transaction.dirtyComponentsLength;
// scr: -----------------------------------> sanity check...
  for (var i = 0; i < len; i++) {    var component = dirtyComponents[i];
    var callbacks = component._pendingCallbacks;    component._pendingCallbacks = null;
    // scr: ------------------------------> logging...
    ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);    // scr: ------------------------------> logging    if (callbacks) { // scr: -------------> no callbacks...    }  }}
[email protected]/shared/stack/reconciler/ReactUpdates.js

This time we have one dirtyComponents, ReactCompositeComponent[ins] which is the first parameter of ReactReconciler.performUpdateIfNecessary.

performUpdateIfNecessary: function (  internalInstance,  transaction,  updateBatchNumber) {// scr: DEV code...
// scr: DEV code..}
[email protected]/shared/stack/reconciler/ReactUpdates.js

Like most of the other methods in ReactReconciler class, ReactReconciler.performUpdateIfNecessary() will call the component’s same method, ReactCompositeComponent.performUpdateIfNecessary()

performUpdateIfNecessary: function (transaction) {  if (this._pendingElement != null) {    // scr: -----------> condition not applied...  } else if (    this._pendingStateQueue !== null ||     this._pendingForceUpdate  ) {    this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);  } else {    // scr: -----------> condition not applied...  }},
[email protected]/shared/stack/reconciler/ReactCompositeComponent.js

It in turn calls ReactCompositeComponent[ins].updateComponent(). Note that _pendingStateQueue is set right before the logic enters the Transaction context.

updateComponent: function(    transaction,    prevParentElement,    nextParentElement,    prevUnmaskedContext,    nextUnmaskedContext,) {  var inst = this._instance; // scr: ---------------------------> 1)  // scr: sanity check and code that is not applicable this time...
  // scr: ------------------------------------------------------> 2)      var nextState = this._processPendingState(nextProps, nextContext); 
  var shouldUpdate = true;
  if (!this._pendingForceUpdate) {    if (inst.shouldComponentUpdate) { // scr: ------------------> 3)      shouldUpdate = inst.shouldComponentUpdate(        nextProps,        nextState,        nextContext,      );    } else {      if (this._compositeType === CompositeTypes.PureClass) {        // scr: ---------------> it is ImpureClass, not applicable...      }    }  }
  this._updateBatchNumber = null;  if (shouldUpdate) {    this._pendingForceUpdate = false;    // Will set `this.props`, `this.state` and `this.context`.    this._performComponentUpdate( // scr: --------------------> 4)      nextParentElement,      nextProps,      nextState,      nextContext,      transaction,      nextUnmaskedContext,    );  } else {    // scr: code that is not applicable this time...  }},
[email protected]/shared/stack/reconciler/ReactCompositeComponent.js

1) obtain the external ReactComponent instance (App) from ReactCompositeComponent[ins]._instance {Figure-I};

2) merge the partial state in ReactCompositeComponent[ins]._pendingStateQueue ({desc:’end’,color:’green’}) and existing states using Object.assign();

_processPendingState: function(props, context) {  // scr: -------> obtain the App {Figure-I}  var inst = this._instance;  var queue = this._pendingStateQueue;  // scr: code that is not applicable this time...
  var nextState =     Object.assign({}, replace ? queue[0] : inst.state);
  for (var i = replace ? 1 : 0; i < queue.length; i++) {    var partial = queue[i];    Object.assign(      nextState,      typeof partial === 'function'        ?, nextState, props, context)        : partial,    );  }
  return nextState;},
[email protected]/shared/stack/reconciler/ReactCompositeComponent.js

3) this is the lifecycle function that is provided to the developers to avoid reconciliation (the following processing logic) from being executed in case setState() does not change the critical states;

Most likely you do not need this function

4) enter the next stop.

_performComponentUpdate: function(    nextElement,    nextProps,    nextState,    nextContext,    transaction,    unmaskedContext,) {  var inst = this._instance; // scr: {Figure-I}
  // scr: code that is not applicable this time...
  // scr: invoke App's life cycle method if defined  if (inst.componentWillUpdate) {     inst.componentWillUpdate(nextProps, nextState, nextContext);  }
  // scr: code that is not applicable this time...    inst.state = nextState;...
  this._updateRenderedComponent(transaction, unmaskedContext);
  // scr: queue App's life cycle method if defined  if (hasComponentDidUpdate) {...  }},
[email protected]/shared/stack/reconciler/ReactCompositeComponent.js

It simply sets the App’s state to the newly merged one. And calls this._updateRenderedComponent() which is the entry point of the diffing algorithm.

The call stack so far:

...|~~~  |-runBatchedUpdates()    |-performUpdateIfNecessary()      |-ReactCompositeComponent[ins].performUpdateIfNecessary()        |-this.updateComponent()          |-this._processPendingState()          |-this._performComponentUpdate()                     ___            |-this._updateRenderedComponent()                   |...                                                          diffing

Then the logic processes to the diffing algorithm.

Virtual DOM

Before we start examining the Diffing algorithm, we better have a consent about what exactly are virtual DOMs, as the term did not appear in the code base.

Here I paste an image from {post five} as a reminder:


The ReactElements are the virtual DOMs we are going to agree on. {post five} also discussed how the virtual DOMs of this kind are initially established.

In MVC terms ReactElements are modals, and ReactDOMComponents are controllers.


The figure above gives the old virtual DOM tree. A new one (‘s modal) will be generated with ReactCompositeComponent[ins]._renderValidatedComponent(). The process is the same as in {post four}.

ctl-f “in _renderValidatedComponent()”

_updateRenderedComponent: function (transaction, context) {  var prevComponentInstance = this._renderedComponent; // scr: -> 1)
  // scr: ------------------------------------------------------> 2)  var prevRenderedElement = prevComponentInstance._currentElement;
  // scr: create a new DOM tree  var nextRenderedElement = this._renderValidatedComponent();
  var debugID = 0;
  // scr: DEV code...
  if (shouldUpdateReactComponent( // scr: ----------------------> 3)        prevRenderedElement,        nextRenderedElement)  ) {    ReactReconciler.receiveComponent( // scr: ------------------> 5)      prevComponentInstance,      nextRenderedElement,      transaction,      this._processChildContext(context)    );  } else { // scr: ---------------------------------------------> 4)  // scr: code that is not applicable this time...  }},
[email protected]/shared/stack/reconciler/ReactCompositeComponent.js

1) obtain ReactDOMComponent[6] through ReactCompositeComponent[ins] {Figure-I};

2) cascading call of React.createElement() in App[ins].render() to create the new DOM tree {post four}, in which the only different DOM node is:

3) the first comparison of diffing algorithm is between types of the old and new root elements;

4) if they are not the same, build the new tree from scratch — the component mounting process is similar to that discussed in {post five};

whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch

5) if the a the same so, start the DOM updating process.

updateComponent: function(  transaction,  prevElement,  nextElement,  context) {  var lastProps = prevElement.props;  var nextProps = this._currentElement.props;
  // scr: code that is not applicable this time...
  // scr: ------------------------------------------------------> 1)  this._updateDOMProperties(lastProps, nextProps, transaction);
  // scr: ------------------------------------------------------> 2)  this._updateDOMChildren(lastProps, nextProps, transaction, context);
// scr: code that is not applicable this time...},
[email protected]/dom/shared/ReactDOMComponent.js

1) get the props from the old virtual DOM (lastProps) and the newly created one (nextProps).

2)ReactDOMComponent._updateDOMProperties() checks the old and new versions of a DOM’s props, and calls CSSPropertyOperations.setValueForStyles() to update the DOM if different;

3) ReactDOMComponent._updateDOMChildren() checks the old and new versions of a DOM’s content (text, inner HTML), and calls ReactDOMComponent.updateTextContent() to update the DOM’s (text) content if different.

The static call stack:

...                                                            ___ReactReconciler.receiveComponent()      <----------------|      |  |-ReactDOMComponent.receiveComponent()                 |      |    |-this.updateComponent()                             |      |      |-this._updateDOMProperties()                      |   diffing        |-CSSPropertyOperations.setValueForStyles()      |      |      |-this._updateDOMChildren()                        |      |        |-this.updateTextContent()                       |      |        |-recursing children (not the focus this time) --|      |                                                               ---

By observing the static call stack, it is not hard to deduce the relationship of these functions and the funtionality recursion in general.

1) one iteration of this recursion updates the properties of one virtual DOM;

2) ReactDOMComponent.updateDOMChildren() is also responsible to go through the current virtual DOM’s direct children and invoke the next iteration for each of them.

note that sub DOM recursing is not the focus of this post

I collapse some method calls in the above call stack,

|-ReactReconciler.receiveComponent()  |-ReactDOMComponent[n].receiveComponent()    |-this.updateComponent()

and draw the call stack in action for clarity:

...|-ReactDOMComponent[6].updateComponent()  |-this._updateDOMProperties() // scr: ----> same  |-this._updateDOMChildren    |-recursing children (not the focus this time...)      |-ReactDOMComponent[4].updateComponent()        |-this._updateDOMProperties() // scr: ----> same        |-this._updateDOMChildren          |-recursing children (not the focus this time...)            |-ReactDOMComponent[2].updateComponent()              |-this._updateDOMProperties() // scr: ----> same              |-this._updateDOMChildren     // scr: ----> same            |-ReactDOMComponent[3].updateComponent()              |-this._updateDOMProperties() // scr: ----> same              |-this._updateDOMChildren     // scr: ----> same      |-ReactDOMComponent[5].updateComponent()        |-this._updateDOMProperties()          |-CSSPropertyOperations.setValueForStyles()        |-this._updateDOMChildren          |-this.updateTextContent()

`ReactDOMComponent._updateDOMProperties()` —check if a DOM changedThis is the overlooked method in {post three *6}In this article we focus on only STYLE updating related code.

_updateDOMProperties: function(lastProps, nextProps, transaction) {  var propKey;  var styleName;  var styleUpdates;// scr: --------------------------------------------------------> 1)  for (propKey in lastProps) {    if (      nextProps.hasOwnProperty(propKey) ||      !lastProps.hasOwnProperty(propKey) ||      lastProps[propKey] == null    ) {      continue;    }    if (propKey === STYLE) {      var lastStyle = this._previousStyleCopy;      for (styleName in lastStyle) {        if (lastStyle.hasOwnProperty(styleName)) {          styleUpdates = styleUpdates || {};          styleUpdates[styleName] = '';        }      }      this._previousStyleCopy = null;    } else if ... {      // scr: not the focus this time...    }  }// scr: ----------------------------------------------------> end 1)
for (propKey in nextProps) {    var nextProp = nextProps[propKey];    var lastProp = propKey === STYLE        ? this._previousStyleCopy        : lastProps != null ? lastProps[propKey] : undefined;    if (      !nextProps.hasOwnProperty(propKey) ||      nextProp === lastProp ||      (nextProp == null && lastProp == null)    ) {      continue;    }    if (propKey === STYLE) {      if (nextProp) {        // scr: DEV code...       // scr: -------------------------------------------------> 2)       nextProp = this._previousStyleCopy = Object.assign({}, nextProp);      } else {        this._previousStyleCopy = null;      }      if (lastProp) { // scr: ----------------------------------> 3)        // scr: the comment applies here -----------------------> a)        // Unset styles on `lastProp` but not on `nextProp`.         for (styleName in lastProp) {          if (            lastProp.hasOwnProperty(styleName) &&            (!nextProp || !nextProp.hasOwnProperty(styleName))          ) {            styleUpdates = styleUpdates || {};            styleUpdates[styleName] = '';          }        }        // scr: the comment applies here -----------------------> b)        // Update styles that changed since `lastProp`.        for (styleName in nextProp) {          if (            nextProp.hasOwnProperty(styleName) &&            lastProp[styleName] !== nextProp[styleName]          ) {            styleUpdates = styleUpdates || {};            styleUpdates[styleName] = nextProp[styleName];          }        }      } else { // scr: -----------------------------------------> 4)        // Relies on `updateStylesByID` not mutating `styleUpdates`.        styleUpdates = nextProp;      }    } else if (...) {      // scr: DEV code...    }  }  if (styleUpdates) { // scr: ----------------------------------> 5)    CSSPropertyOperations.setValueForStyles(      getNode(this),      styleUpdates,      this,    );  }},
[email protected]/dom/shared/ReactDOMComponent.js

1) if the new props do not contain “style” at all,

...if (nextProps.hasOwnProperty(propKey) ||...) {  continue;} // scr: else, do something...

mark all the existing style entries as ‘remove’, note that existing styles are stored in this._previousStyleCopy in 2);

2) copy nextProp (current styles) to this._previousStyleCopy;

3) if there are existing styles,

var lastProp = propKey === STYLE        ? this._previousStyleCopy...
if (lastProp) {...

update by a) marking existing style entries that are not in nextProp as ‘remove’ and b) marking style entries in nextProp as ‘add’ if it is different from the existing entry on the same key;

4) if not, simply mark all the styles in nextProp as ‘add’;

5) conduct the real DOM operations. Note that getNode() is an alias to ReactDOMComponentTree.getNodeFromInstance() that uses ReactDOMComponent._hostNode to get the associated DOM element {Figure-III} {post three}.

ctl-f “ReactDOMComponent[ins]._hostNode”`CSSPropertyOperations.setValueForStyles()` — update props

setValueForStyles: function(node, styles, component) {  var style =;  for (var styleName in styles) {    if (!styles.hasOwnProperty(styleName)) {      continue;    }
    // scr: DEV code or code that is not applicable...    if (isCustomProperty) {...    } else if (styleValue) {      style[styleName] = styleValue;    } else {        code that is not applicable this time...    }  }},
[email protected]/dom/shared/CSSPropertyOperations.js

Here the only line that is applicable here is style[styleName] = styleValue; that set the with styles marked in the previous method.

As a result,[‘color’] = ‘red’.

`_updateDOMChildren` —check if a DOM’s content changed (and recurse its children)We omit the dangerouslySetInnerHTML related code and focus only on hot paths

_updateDOMChildren: function(  lastProps,  nextProps,  transaction,  context) {  var lastContent = CONTENT_TYPES[typeof lastProps.children]      ? lastProps.children      : null;  var nextContent = CONTENT_TYPES[typeof nextProps.children]      ? nextProps.children      : null;
  // scr: code that is not applicable...
// Note the use of `!=` which checks for null or undefined.  // scr: used by recursing children, to be continued...  var lastChildren = lastContent != null ? null : lastProps.children;  var nextChildren = nextContent != null ? null : nextProps.children;
  // scr: code that is not applicable...  if (lastChildren != null && nextChildren == null) {    // scr: recursing children, to be continued...    this.updateChildren(null, transaction, context);  } else if (lastHasContentOrHtml && !nextHasContentOrHtml) {    // scr: DEV code and code that is not applicable...  }
  if (nextContent != null) {    if (lastContent !== nextContent) {      this.updateTextContent('' + nextContent);      // scr: DEV code...    }  } else if (nextHtml != null) {    // scr: code that is not applicable...  } else if (nextChildren != null) {    // scr: DEV code...
    // scr: recursing children, to be continued...    this.updateChildren(nextChildren, transaction, context);  }},
[email protected]/dom/shared/ReactDOMComponent.js

The only line that is applicable here is

 this.updateTextContent(‘’ + nextContent);

`ReactDOMComponent.updateTextContent()` — update content

Presumably ReactDOMComponent.updateTextContent() is used to set the text from ‘start’ to ‘end’. However, the call stack of this method is a bit deep for this simple operation,

updateTextContent: function(nextContent) {  var prevChildren = this._renderedChildren;  // Remove any rendered children. scr: -------> the comment applies  ReactChildReconciler.unmountChildren(prevChildren, false);  for (var name in prevChildren) {    // scr: sanity check...  }  // Set new text content. scr: ---------------> the comment applies  var updates = [makeTextContent(nextContent)];  processQueue(this, updates);},
function processQueue(inst, updateQueue) {  ReactComponentEnvironment.processChildrenUpdates(inst, updateQueue);}
[email protected]/shared/stack/reconciler/ReactMultiChild.js

Here ReactComponentBrowserEnvironment is injected as ReactComponentEnvironment.

...  processChildrenUpdates:    ReactDOMIDOperations.dangerouslyProcessChildrenUpdates,...
[email protected]/dom/shared/ReactComponentBrowserEnvironment.js

and .processChildrenUpdates is an alias to ReactDOMIDOperations.dangerouslyProcessChildrenUpdates

dangerouslyProcessChildrenUpdates: function(parentInst, updates) {  var node = ReactDOMComponentTree.getNodeFromInstance(parentInst);  DOMChildrenOperations.processUpdates(node, updates);},
[email protected]/dom/client/ReactDOMIDOperations.js

The ReactDOMComponentTree.getNodeFromInstance() method is discussed in the previous section.

processUpdates: function(parentNode, updates) {  // scr: DEV code...
  for (var k = 0; k < updates.length; k++) {    var update = updates[k];    switch (update.type) {      // scr: code that is not applicable...      case 'TEXT_CONTENT':        setTextContent(parentNode, update.content);        // scr: DEV code...        break;...
[email protected]/dom/client/utils/DOMChildrenOperations.js

As expected, the last card in the stack is setTextContent() which sets Node.textContent directly. This method is covered in {post V} so I will not repeat its implementation.

The sub call stack of ReactDOMComponent.updateTextContent() and the ‘end’ result of it:

|-ReactDOMComponent.updateTextContent()  |-processQueue()    |-ReactComponentEnvironment.processChildrenUpdates()    |=ReactDOMIDOperations.dangerouslyProcessChildrenUpdates()      |-ReactDOMComponentTree.getNodeFromInstance()      |-DOMChildrenOperations.processUpdates()        |-setTextContent()          |-Node.textContent = 'end'

In the next post we are going to further investigate the diffing algorithm by observing the mutation of DOM trees, which also concludes this series (for a period of time). I hope you will feel more

next time when using setState(). Thanks, and I hope to see you the next time.

Originally published at

Understanding The React Source Code VIII was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.

This article is originally written and posted on  Visit today for all Crypto News and Information.

Leave a Reply

Your email address will not be published. Required fields are marked *