Backbone router with React Components

Patrick Tolosa
3 min readJun 29, 2017

--

React has become a leading framework for front-end work and most React tutorials deal with how to create a React application.

The reality is that while new projects can be ‘easily’ created in React, Migrating existing projects is a bit more difficult.
Our team faced such a difficulty — We were tasked with replacing a website in production with React Components, with minimal regression and without hitting performance.

There are two obvious solutions, You can either rewrite your entire app or migrate slowly, replacing small Components until the entire thing is replaced by React, inside out.

Since rewrite was not a possibility, Our team decided to take the 2nd approach.
After reading all the docs, online tutorials, scoured stack overflow and left our yearly tribute to the JS gods, we realized there’s one key thing that’s never really addressed — What happens to React Components if their DOM elements are removed from the site in a non React way(i.e element.remove() ?

An extremely common case for this is when you add some nice gallery Component to one of your pages, and then the user navigates to a different page. In our case the routing was managed by Backbone, which deals with routing in a very aggressive manner: Remove the entire “page-container” div and replace it’s content with the new page.

When a React Component is mounted, it creates an element with a data attribute of ‘data-reactroot’ under it.
To reference, there are three DOM nodes of interest:

  1. The mounted Element, this is the Element that was passed to ReactDOM.render as a parent, We’ll call this the container.
  2. Any parent node of the container, we’ll refer to them as parent(s)
  3. The created Component’s element, we’ll call this the ReactRoot

Removing the Container

Removing the Container from the DOM will keep the ReactRoot Element alive and well.. in memory. React will keep a reference to this DOM node and will keep updating it.

In the above snippet, the console.log will keep printing ‘Woot’, the entire Component will still be functional, even though it’s not on the DOM.
A Cool side effect though is that you can re-insert the removed DOM fragment to the DOM, React doesn’t care if it’s attached to the DOM or not.

At this point it’s still possible to unmount the component, using ReactDOM.unmountComponentAtNode on window.div will successfully unmount the Component.

Removing Any parent

Shockingly enough, the same thing happens here; The DOM fragment is still in memory, React will keep updating the Node, you can reattach it, you can also re-insert it, most importantly, you can still unmount it

Removing the ReactRoot from the DOM.

This is where things get a bit sad, Once you remove the ReactRoot from the DOM, we did not find any way to unmount it, while this shouldn’t happen normally, it’s especially important to pay close attention to this fact when touching the DOM in the old jQuery near a ReactRoot.
TLDR — This will result in a memory leak.

Un-mounting a removed DOM node

So up until now we discussed what’s possible, but not how it’s done.
It seems that the two base cases (1 and 2) still need us to somehow know which nodes have been removed from the DOM, without adding events all over the website that trigger a node removal

Enter — MutationObserver! (limited support, your milage may vary)

In short — The MutationObserver allows us to tap into the DOM removal routines, whenever a DOM node is detached from (or attached to) the DOM tree, the mutation observer is triggered with the DOM node.
Our solution was simply listening to all removed DOM nodes, iterating over them and finding the ones that have a ReactRoot as children, if you’re a DOM element with a ReactRoot Element as a child, we can safely unmount you at this point.

Transitioning from Backbone or jQuery to React can be daunting, but we believe it’s worth it for the features and scale a new framework brings to the table.

I hope this article will facilitate others in migrating from Backbone, jQuery etc. to React, this memory leak was a major thing we had to deal with and one of our main blocking points.

I’ll be following up with an article on how exactly this process works (migration) in detail.

--

--