What do we notice? We have a header and some rows. Simple right? Well, yes, and no. The problem here is that we have a scrollable container with a bunch of rows, and a disparate header that should be in sync with all of these rows.

What’s the big deal you say? Well let’s look at the hypothetical DOM structure this presents:

<table>

<thead> …headers </thead>

<div style=”overflow-y:auto”>

<tbody>

<tr></tr>

…more rows

</tbody>

</div>

</table>

I’m sure you’ve guessed by now that this just isn’t going to work with React. If we tried this DOM, and believe me, we did, we’d get the following error:

Warning: validateDOMNesting(…): <tbody> cannot appear as a child of <div>

Which makes sense, since yeah, that’s pretty gross HTML. I’d also like to note that making a <tbody> scroll nicely seems nearly impossible. But since we needed to be able to resize columns, and we didn’t want to have to rely on JavaScript to keep our headers and our cells aligned (because of overall expense), we needed a new solution. We came upon the following solution because although React makes creating invalid HTML impossible, it makes rendering HTML super cheap and efficient:

<div><!--overall component container ->

<table><!--table for just our visible headers-->

<thead>

…headers

</thead>

</table>

<div style=”overflow-y:auto”>

<table>

<thead style=”visbility:hidden;”>

…copy of above headers

</thead> <!-- invisible headers -->

<tbody>

<tr></tr>

…more rows

</tbody>

</table>

</div>

</div>

We end up with a lot more DOM here, but we get something pretty powerful as well. Although we have two HTML copies of our Header component, they are actually the exact same React components (so no extra code required) — being driven by the same slice of Redux state (no extra state required). This was incredibly helpful. When one of the visible headers is resized, although it doesn’t update the corresponding cells automatically, it does update the Redux store with a new width prop. That width is also driving the invisible header’s width, which then triggers the browser to resize the corresponding <td>’s in the scrollable container.

And Viola, we have two disparate tables communicating via Redux state. We are dispatching an action when header actions occur, but we don’t need to keep track of cell width (which would be painful and expensive).

This was one of the first “ah-ha” moments for me, working with React and Redux. I kept running into problems I had solved before with things like jQuery or Angular that React made a little more difficult (like the invalid DOM problem). But when it came to core view-centric problems like having to efficiently update the DOM, or sensibly managing state, React simply made those afterthoughts. I really didn’t need to care at all, so I didn’t.

The ‘Making it Fast’ Problem

React is fast, but there’s always ways to optimize for speed. Although we went through a shouldComponentUpdate phase, where we nailed down each component so they only redrew when needed, I’m going to talk about how we optimized connect for our problem space.

Connect is a decorator exposed by Redux that essentially hooks a component up to the Redux store so that it can read out of the state atom. There is a bunch more to it (that’s incredibly important for performance optimization) but for our purposes, that’s probably enough. The only other thing I’ll say is that we incur a bit more overhead since all connected components are “listening” for state changes, and must run through mapStateToProps anytime the store gets updated.

We can see from our final DOM structure, that we’re going to have a bunch of components. The problem that grid poses more than other UI components is scale. For example, in our app we often had instances of thousands of records on a single page, each record with dozens of properties we wanted to display. That’s a lot of DOM, but it’s also a lot of event listeners, and JavaScript objects in memory, etc.

We started out by connecting everything — hey, everything needed to read from the store! That meant our header, the scroll-container, the footer, every row, and yes, every single cell. It worked — kind of. Sure it rendered, but interactions were too sluggish and redraws weren’t 60 fps crisp. We needed to do something different.

That’s all the details on the problem for now, since I’m going to discuss this in the “Things We Messed Up” section. But I will just say that our final solution (which maybe we should rethink) is to only have a single connected component — the grid container. That way we only run through mapStateToProps once, and we pass our props all the way down the chain, letting shouldComponentUpdate handle the rest. This resulted is some untenable code at times, but a lightning-quick reconciliation/render cycle.

The ‘What do we Export’ Problem

That brings us to a non-technical problem. At the time of inception, building a “Redux-Component” was a relatively new idea. There were React components that exposed a single export — the component itself. Sometimes they bundle CSS dynamically, and sometimes you needed to import a stylesheet manually.

But Redux is a little different because it brings the added dependencies of a store, reducers, and actions. Did these need to be exported? We didn’t know. We guessed they did, but to be honest, we weren’t sure until we started using the component ourselves.

We ended up exporting everything — including the demo store. We wanted the grid to run on its own so people could `git clone` and see how it worked themselves, kind of like a UI REPL. But we didn’t want to impose any pattern beyond the simplest usage. It’s via that thinking that we came up the following exports:

export const modules = {

Actions,

Grid,

Reducers,

applyGridConfig,

Store

};

It’s obvious to us now that the actions were a necessary export since they turned out to be the public API for the grid — how you communicate information to the component via dispatch. We also exposed a number of reducers, each responsible for their own slice of the grid state (i.e. data, columns, editor, pagination). In my mind, this made dealing with grid as heavy or as light as the importing developer wanted. But now I sort of feel like that’s one of the more annoying parts of the component. It makes for cleaner code inside grid, but for a more annoying API. Oh well, so it goes.

We also exported a couple utilities, and this is the area where I think we will spend our near term efforts — building some more utilities that make working with grid a little simpler.

We decided to include CSS dynamically at runtime, but to make it optional. By default the CSS will be appended to the document, but can ignored by setting a property via the applyGridConfig function. One of the simple joys for me, in this new brave React world, is that components can truly be self-sufficient and sandboxed — it always irked me that I needed to include CSS via <link rel> tags with jQuery components.

Things We Messed Up

This section could and should be the longest part of this story. But it’s not going to be, probably out of self-preservation.

Using ImmutableJS the Wrong Way

This brings us back to connect. Since we were worried about the connection cost, we simply reduced the number of decorated components we were authoring — to 1. This got us nearly the whole way, except that we saw big lags in performance when the data-set got large (1000+), and that didn’t sit well with us.

We starting diagnosing the problem using React Perf tools and saw that we were spending almost no time in wasted cycles. Here’s the readout from React-Perf: