Reparenting is now possible with React

How I implemented Reparenting with a few lines of code

Paolo Longo
The Startup
7 min readMay 23, 2020

--

React-Reparenting

I am designing an app similar to Trello. On the main page, I want some vertical Lists and some Cards that can be dragged from one List to another.

How can I transfer a Card component after dragging? With React it seems quite easy. To change the Parent component of a Child component, the components have to be re-rendered with that Child in its new Parent.

In the same way, I can transfer a <Card> into a new <List>.

I implement a first draft of the code and try it, I take a Card with the mouse and drag it between the various Lists. The transfer takes place, but, unfortunately, the Card component is unmounted, re-mounted, and loses its internal state.

Moreover the feedback from the drag animation isn’t so positive. When I perform some drags quickly in succession, the App slows down and for a few moments there is a considerable loss of frames.

In fact, the DOM elements of the Card are recreated from scratch and this is having a negative impact on performance. Also, one of the elements is a scrollable <div> that loses its scroll position, I guess other elements such as <video> and <audio> can have similar problems.

With some effort, I can redesign the App to use Card components without a local state, but in any case I cannot avoid that the DOM elements are recreated.

Is it possible to prevent the component from being re-mounted?

Well, if you are reading this article it is likely that the answer is positive :), but when I asked myself the question for the first time I did not find a definitive answer, probably because it was not there yet. Let’s continue with the story.

I start looking for an answer in the React repository on Github, maybe there is something useful in the issues section. I find there is a term for what I’m looking for, and it’s Reparenting.

“Reparenting aims to improve both the Developer and the User Experience.”

Some open issues confirm that React does not yet provide specific APIs to handle it, my hopes that something like React.transferComponent( ) exists quickly fade away.

An approach I discover is ReactDOM.unstable_renderSubtreeIntoContainer( ), the name looks cool but the unstable tag and the fact that this API has been deprecated are enough to make me look for something else. Searches continue on Medium, Dev.to, and other platforms, the only possible solution seems to be the use of the Portals. A Tweet by Dan Abramov definitely convinces me to try them.

The portals approach

I open the React documentation in the Portals section. I start reading the guide and doing some tests to get familiar with these APIs.

I know that I cannot move a component elsewhere in the App or it will be re-mounted, so every Child component must be part of the same Parent.

Should I use a portal for each Child? That way I could decide in which container element to render each of them. But how do I create containers? Do I have to write something like document.createElement('div') 🤨? I could instead use ref to other components. Where do I render those components? Refs are empty initially, should I force a second render? I wanted each Parent to provide a different Context, How can I do that if I am forced to use only one Parent?…

What a mess, the more I try to implement it, the more forced the approach seems to me. It doesn’t give me the feeling of being very “reactive”, probably because portals have been designed for other purposes:

“Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.” — React docs.

This process is more related to the DOM, at the “React level” the Child is still part of the same Parent, not exactly what I am looking for.

The new solution

Maybe I’m looking for a solution in the wrong place, it is possible that, if it exists, it is more internal to React than I think.

What I know is that React represents my App with a tree of instances, where each instance corresponds to a component. When re-rendering a part of the App, its sub-tree is recreated and compared with the old one, so as to find the changes that have been made and update the DOM.

The image is for illustrative purposes only

Due to the way this comparison is implemented, there is no method to make React aware of the transfer of a component. Indeed, If I try to re-render a Card component somewhere else, the result will be the unmounting of the component and the mounting of a new one.

How can I change this behavior? I could try to interact with the internal tree, find the instance of the Card that I want to transfer, and insert it in the new List. In this way, after a re-rendering, both the old and the new tree would have the transferred Card in the same place and the comparison would not cause the re-mount of the component, It might work!

Before starting to design a solution, to avoid running into dead ends I impose some constraints that the final result must respect:

  • It must not rely on any unstable method
  • Reparenting must be able to work without redesigning the App
  • It must respect the philosophy and patterns of React

I have a solid starting point, now I have to understand how these react internals are actually implemented. I find out that starting from version 16, React rolled out a new implementation of that internal instances tree named Fiber. I read some articles about it to get a more complete picture, and when I think I have a fairly broad view on the topic, I start to browse the React source code in search of a solution.

After several days of testing and research, I finally have a first draft of code to try, inside a file named react-reparenting.js. I import it into my App, add a few lines of code, and… It works! The Card is not re-mounted and the goals that I have set for myself have all been respected.

This story can finally have a nice ending, I can continue the development of my App. Maybe, for the next obstacle that I will face, I will find a story like this to read.

The end of the story

This story ends with the publication of the package on Github and with the writing of this article. Before presenting it, I want to share with you what my vision is at the end of this project.

I strongly believe that Reparenting is not only a way of managing these situations, but The way, and I also believe that in the future React will implement it natively.

In my opinion, the reason why this feature has not yet been implemented is that the cases in which it is really necessary are not many. Often the components to be transferred are stateless and very simple, so it is an acceptable compromise to re-mount them since the difference in performance is almost zero, and there is no state or lifecycle to be interrupted.

I’m not saying that React will implement Reparenting as it has been implemented here, or that the APIs that will be provided will be similar to these, but I hope this package, thanks also to its simplicity, can lay the foundations for the use and diffusion of Reparenting.

“Unmounting one component and mounting another identical one is just a simple compromise that works in most cases. The component should always be transferred, without its lifecycle being interrupted.”

You can find the package on Github.

On the GitHub page you will also find the documentation and links to various examples on Codesandbox. Now, let’s see a simple implementation.

First, let’s define a <Child> component, we will use a very simple one.

Now we can use the <Reparentable> component, it has to be the direct parent of the children to reparent. Each <Reparentable> must have a unique id.

Now we can reparent a <Child>. First we have to send its internal instance using the sendReparentableChild( ) method, then we just have to re-render the App. The transferred component will not be re-mounted.

That’s all. It is also possible to create a custom Parent component and use the <Reparentable> inside it.

Special thanks

During the development of this project, I thought that I would lose my mind managing every use case (context, memo, some edge cases with fibers…). With pleasant surprise React worked in each of these cases without modification, a sign of the amazing work that the React Team has done over the years.

I want also to thank the authors of these amazing articles, without them the work would have been longer and more tedious.

--

--

Paolo Longo
The Startup

21 years old Italian student of computer engineering at the Milan Polytechnic