Global key for Reconciliation


(Ivan Malyugin) #1

According to docs, current reconciliation only works with subtree changes within the same parent, but it doesn’t really seem like something hard to scale up to work with subtrees moved anywhere in a single render.

I’m trying to force react to properly reconcile nodes of dynamic components tree (for adaptivity on window resize), with structure like this:
Desktop:

A
|_B
  |_C
  |_D

Into Mobile:

A
|_ C
|_ D

But that would currently force redraw of the entire C/D subtree, and If I’m using that somwhere on top layer of the site, It would be forced to redraw the entire website while losing all the states on the way.

So at the moment it would work the following way:
Desktop:

A
|_B
  |_C
  |_D

Mobile:

A
|_ B -- not found in new structure, remove
|_ C -- not found in shadow dom, redraw completely
|_ D -- not found in shadow dom, redraw completely

If I understand it right, wouldn’t it be rather simple to add another feature such as “gkey”. When defined, behavior changes to the following:

Desktop:

A
|_B
  |_C -- gkey="MyGlobalComponent_C"
  |_D -- gkey="MyGlobalComponent_D"

-- React Render Hash Table
["MyGlobalComponent_C"] = C
["MyGlobalComponent_D"] = D 

Mobile:

A
|_ B -- not found in new structure, remove (or save into hashtable for when it's back again, at least the state)
|_ C -- new child; has gkey; found in render hashtable; move from old location
|_ D -- new child; has gkey; found in render hashtable; move from old location

Does react roadmap have something simillar planned or maybe there is already something that could solve my problem from another approach. Leaving rewriting react subclasses myself to the last resort. :slight_smile:

Simplified example of the issue is shown in jsfiddle When you resize over 640 up/down, even the most simple structure within a single element forces a redraw (counter increases as component gets remounted) dispite key definition. In my case thou, I have a completely dynamic tree that may change in any imaginable way.


(Ccorcos) #2

How often do you anticipate C and D losing their parent B, but still having the same props? Because if the props change, they’ll have to be re-rendered anyways…


(Alex Guerra) #3

@ccorcos If it’s in response to a window size change, probably every time? And being re-rendered vs rendered from scratch should have very different performance characteristics I think.

@IMalyugin I vaguely remember having seen some discussion about this. I’m not a contributor or anything but my gut reaction is that I doubt we’ll see it any time soon? Not that it’s a bad idea, just that most people probably don’t need it and it raises some hairy questions.

Maybe a solution is possible in userspace though? Envision a higher order component that ReactDOM.renders to some hidden global cache and manually handles moving the element using lifecycle hooks. Timing will be hard to work out I think, but it’s a possible avenue if it’s a major pain point for you?


(Ivan Malyugin) #4

@ccorcos, exactly as @HeyImAlex mentioned, that kind of rerendering is going to happen each and every time window size crossed a breakpoint.

There are two major problems, one being rendering from scratch, possibly the entire page, if that swap occurs close to document root, and the second one - losing the state of the entire site.

While the entire idea of site adaptivity by rerendering may sound very wrong, considering possible freezing and refusing the usage of browser-optimized media queries, in my case that is something inevitable.

I have a wywiwyg editor, containing BEM isolated keystones (blocks), that are used to make the site. Each block added in has a global ID, and it’s all great for desktop, but there’s adaptivity issues. In mobile layout, I want to have control of how the page looks. So when I switch out to mobile view, I need to be able to toss the structure in any way I like, change fields, change order, nesting, everything.

I don’t care that much about people that resize page all the time, having them frozen for half a second is still better than splitting desktop and mobile.

@HeyImAlex, having every nested node call a separate rendering method seems like something react devs wouldn’t like. That would require over 2000 dom render calls on one page. Infact I would have to handle the entire dom change magic. I thought about using flux global state everywhere, then my state loss issue wouldn’t be a problem. But rendering page from scratch all the time would still cause a lot of problems.


(Alex Guerra) #5

FYI here’s the relevant issue. I think the linked gist is where I read most of the discussion.


(worg) #6

If your main concern is losing state, then stop using state and take advantage of your data store [Flux, Redux, you name it] stop using state and move it to the store.


(Ivan Malyugin) #7

@HeyImAlex, yep, couldn’t find that thread. I believe that my case provides an obvious real life usage example. It really feels like a next step to react - no more complicated adaptive components, define a set of components and interchange them for adaptivity with no overhead.

@worg, that’s closest solution at the moment, would be a shame to strip react of its essential part by completely rejecting local state, infact all the imported components would stop working, unless rewritten.

Possibly how hard implementing support for a global key could be? Did I miss something in my first post, making this task a whole level harder?


(Develerltd) #8

It seems to me that it would probably be easier to restructure the dom such that the additional desktop elements of B don’t impinge on the rending of C and D, ie B has a child (b2) as well as C and D, and the desktop elements are rendered into b2 instead of C and D being wrapped by them.


(Ivan Malyugin) #9

@develerltd, B element is not an “additional desktop element to be rendered”. It’s a wrapper, possibly something with its own css/js specifics. The whole core idea is that you do not keep all the adaptivity rules within same elements and don’t make site-level huge elements that force redraw everything, you keep your components small and interchange them with adaptivity, but the content underneath stays the same, thus the structure changes, sometimes via minor difference, sometimes not.

As of now, I see 3 solutions to keep element states in places:

  1. Keep all the states in flux/redux.
    Performance : 2/10; Simplicity: 5/10

  2. Inherit all components from modified React object, that has “componentWillUnmount” and “componentWillMount” which would save state into global hash table, simillar to flux and restore it (I can do that since every keystone in my system has a unique global id).
    Performance: 0/10; Simplicity: 7/10

  3. Modify react module to support global key hash tables and then recompile the package.
    Performance: 10/10; Simplicity: 0/10


(Alex Guerra) #10

Something like the second option would probably be the easiest for you and the most performant under normal usage. I’m not sure if race-y conditions would be an issue, but certainly nothing that couldn’t be worked around if every element can be globally keyed. Realistically window resizes will rarely happen, so the performance of the change itself isn’t all that important. Even if mounting/unmounting are frequent, you’re not serializing the data so the cost of storage is literally one Map.set call, which to me means that the common case overhead would be very very small.

Redux will require you to do a lot of work pulling logic out and wiring things up and may negatively impact performance, especially if you’ve got a lot of small actions: every event has to go through the global dispatcher song and dance, and there’s a good bit of overhead associated with that. However it has many other benefits, mostly around “developer experience”.

The third option seems like a complete non-starter considering the work you’d need to do. The react codebase is pretty complex and this would take changes to the reconciler, where I think there’s already or soon will be a lot of new activity around fibers. Even if you made it happen, the burden of maintaining the fork just wouldn’t be worth it.


(Robert Wensman) #11

Global keys would be brilliant! Especially when we move to responsive designs where elements are expected to move around as the user changes screen orientation etc. It would be great if React could move components to other parts of the DOM without causing them to loose state. I am sure it could improve performance also.