Monday, June 18, 2018

React: Get component order within hierarchy

Leave a Comment

I'm trying to implement helper components for CSS Grids. I have something like (prepare yourself):

<ColumnContainer columns={[ "1em", "1fr", "auto", "auto", "1em", "auto"]}>   <ColumnContainer.Row>     { rowIdx => (       <div style={{ gridRow: rowIdx, gridColumn: "1 / 3" }}>       </div>     )}   </ColumnContainer.Row> </ColumnContainer> 

ColumnContainer:

  • Is a container div with display: grid and various grid properties set up
  • Is also a context provider

Then, ColumnContainer.Row:

  • Is basically a context consumer
  • Takes a function as a child
  • Doesn't need to be a direct child of ColumnContainer - hence using context

The context provided is an integer, layoutVersion, which is incremented whenever the set of rows is intended to change (to trigger a re-render), and - hack of hacks - an empty array.

The idea is, as each ColumnContainer.Row renders, it adds itself (could be any object) to the array whose reference is passed in the context, and renders the child function with the size of the array as the parameter (row index).

Believe it or not, this works, for the first render and if rows are just added to the end.

However, when components are added in the "middle" of the component DOM, the resulting rendered rows are out-of-order (but not overlapping). Meaning, I think, that in the case of a new layout version (re-render), all the ColumnContainer.Rows are re-rendered, but not necessarily in the 'natural' order they are in, i.e. in the DOM.

My guess is that depending on components to have render() called in a certain order is a bad idea, as well as modifying the contents of context properties in render().

What are my other options - what I really want is to know the 'natural' order of descendent nodes within a component tree. If they were direct child elements, I'd guess it would be easy - in my case though I have nested components which can output rows.

2 Answers

Answers 1

I have found a dodgy/hacky solution, by having child components 'register' themselves (a unique ID and ref) with the container component via a callback injected in the context.

The container component accumulates these callbacks using functional state updates and setting a flag in the state that a re-calc is required.

The container's componentDidUpdate checks the re-calc flag and compares all of the DOM notes (via the refs) using the browser DOM function compareDocumentPosition. The next state update clears the re-calc flag and includes the order of each of the children's IDs.

This state is then passed down to all children via context, where they can look up their index by ID.

Clearly that sucks but it's all I have for now. Would still like to award bounty to a better solution.

Answers 2

It seems that you rely on render() to provide you with the recursive structure inside your DOM Tree. The reason it works "the first time" is that all the components need to be rendered the first time they are presented to react.

However, subsequent render() calls on ColumnContainer do not imply renders on the sub-components because their state or props might have not changed.

I think you should try the following:

  1. Change your ColumnContainer.Row registration to the ColumnContainer array in a way that the children rembember rowIdx in their state on the first call and then reuse this value for subsequent render calls.

(so to do this effectively you would need:)

  1. Change your registration procedure in two function calls (these two functions should be in your context):

    • a function that gets the rowIdx for a child and remembers it. (this function is only called if you don't have a cached value for rowIdx)
    • a second function that does the work but does not need rowIdx as a parameter. (this function is called just like now, every time).
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment