React mounting performance differences

I've been creating tables in React+Redux in a simple and functional way. I've never really bothered with the performance since it’s been mounting fast enough when there are only a few rows. But I recently was putting together a table of people which could be up to 1000+ rows, and that was enough to cause me to stop and think about performance implications. I had a hunch I could change how I construct the rows for some clear performance wins. That called for some quick benchmarking.

Let me setup a benchmark case: There is a redux store with 2000 people in it that needs to be displayed in a table. Something like this:

{
  data: {
    people_ids: [1, 2, ...],
    people: {
      1: {
        id: 1,
        firstName: 'Some 1',
        lastName: 'One 1',
        team: 'Team 1',
        group: 'Group'
      },
      2: {
        id: 2,
        firstName: 'Some 2',
        lastName: 'One 2',
        team: 'Team 2',
        group: 'Group'
      },
      ...
    }
  }
}

Each table row should display name, team and group, and when you click a row it navigates to a page showing that person. The way I would normally construct it is like this:

// TableV1.js
import React, { Component } from 'react';
import {connect} from 'react-redux';

import TableRowV1 from './TableRow';

function mapStateToProps(state) {
  const ids = state.data.people_ids;
  return {ids};
}

class Table extends Component {
  render() {
    const {ids} = this.props;
    return <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Team</th>
          <th>Group</th>
        </tr>
      </thead>
      <tbody>
        {ids.map(id => <TableRow key={id} id={id} />)}
      </tbody>
    </table>;
  }
}

export default connect(mapStateToProps)(Table);
// TableRowV1.js

import React, {Component} from 'react';
import {connect} from 'react-redux';
import {push} from 'react-router-redux';

function mapStateToProps(state, ownProps) {
  const person = state.data.people[ownProps.id];
  return {person};
}

class TableRow extends Component {
  navigate = () => {
    this.props.dispatch(push(`/person/${this.props.person.id}`));
  }
  render() {
    const {person} = this.props;
    return <tr onClick={this.navigate}>
      <td>{person.firstName} {person.lastName}</td>
      <td>{person.team}</td>
      <td>{person.group}</td>
    </tr>;
  }
}

export default connect(mapStateToProps)(TableRow);

Assume some wrapper component that sets up Redux, React Router, and React Router Redux. Basic idea is that there is a Table.js component that gets the ids from the store, creates the table and table header, and finally maps over the ids to create a TableRow for each one. The TableRow.js component in turn uses the id that is passed to it to get the person from the store, and then renders a tr with the person's details. It then uses push from React Router Redux to dispatch a push action on click to navigate.

I find it a nice clean design, but the performance left something to be desired. There is a lot of work happening in each row. The mapStateToProps function needs to run, both to mount each row and each time state changes, and there is also a need to create a navigate method for each instance of TableRow. I thought it would be better to avoid as much work as possible in the TableRow. That means it would be better to move as much of the work up to the parent Tabel component and then pass down everything needed as props to each row.

After hoisting out as much as possible up to Table the code looked like this

// TableV2.js
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {push} from 'react-router-redux';

import TableRow from './TableRow';

function mapStateToProps(state) {
  const ppl = state.ui.ids.map(id => state.ui.dict[id]);
  return {ppl};
}

class Table extends Component {
  constructor(props) {
    super(props);
    this.navigate = this.navigate.bind(this);
  }
  navigate(id) {
    this.props.dispatch(push(`/person/${id}`));
  }
  render() {
    const {ppl} = this.props;
    return <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Team</th>
          <th>Group</th>
        </tr>
      </thead>
      <tbody>
        {ppl.map(p => <TableRow key={p.id} person={p} navigate={this.navigate} />)}
      </tbody>
    </table>
  }
}

export default connect(mapStateToProps)(Table);
// TableV2.js
import React, {PureComponent} from 'react';

class TableRow extends PureComponent {
  constructor(props) {
    super(props);
    this.handleNav = this.handleNav.bind(this);
  }
  handleNav() {
    this.props.navigate(this.props.person.id);
  }
  render() {
    const {person} = this.props;
    return <tr onClick={this.handleNav}>
      <td>{person.firstName} {person.lastName}</td>
      <td>{person.team}</td>
      <td>{person.group}</td>
    </tr>
  }
}

export default TableRow;

Besides moving all the redux action up to Table, I also made TableRow a PureComponent, and I bind this to handleNav in the constructor to avoid having to create a new method for each instance of the component. The PureComponent not make a big deal for initial mount but can save some rendering time when something causes table to re-render.

So, enough with the details. What was the performance difference? As test data I generated 2000 random people. To measure the performance I used the Chrome Performance Profile, which with User Timings makes it easy to get performance details on React components. I also set CPU slowdown to 4x, to help put emphasis the performance difference.

The results were clear: Time to mount for V1 was 2.63 seconds but the time to mount was 1.03 second. Honestly, the difference was bigger than I thought. That's a solid 60% improvement. Of course, the difference would not be meaningful with only a few rows in the table, but it will amount to a something noticeable once the table gets bigger. I guess I'll have to rethink how I build tables in React in the future.

Taxonomies in Gutenber 0.4

I finally have categories in my sidebar!

This comes from the move to Gutenberg 0.4, with its new taxonomies setup. Getting it migrate took a bit figuring out so I thought I would document it here.

Unlike earlier Gutenberg, which had the predefined taxonomies category and tags, Gutenberg 0.4 allows you to define whatever taxonomies you want. The hierarchy goes like this: At the top you have a list of taxonomies. Each taxonomy consists of a name and a few other options. categories would be a taxonomy with the name categories. Then each taxonomy itself has a list of taxonomy items. Like the categories taxonomy can contain the taxonomy items Webdev and Books.

To setup taxonomies in 0.4 the first step was to define taxonomies in config.toml file. The only taxonomy I used was categories, so I defined it like this:

[[taxonomies]]
name = "categories"
rss = false

Once I had my one taxonomy, categories, defined I needed to update the front matter on all my pages. Where before there was just a category key you could set, there is now a taxonomies section. On this post it looks like this:

[taxonomies]
categories = ["Webdev"]

Categories is now also a list because you can assign multiple taxonomy items in the same taxonomy to a page, like tags used to work. Mappings between a taxonomy items and pages is always many-to-many. You do not need to define taxonomy items, Gutenberg creates the for you based on when you assign them to pages.

The next step was to update the page.html template for the new taxonomies. Where page used to have a category key, it now has a taxonomies key, which is a HashMap from the name of the taxonomy to a list of the names of the assigned taxonomy items. So on this post it looks something like this (expressed in JSON):

"page": {
  "taxonomies": {
    "categories": [
      "Webdev"
    ]
  }
}

You can still use the get_taxonomy_url to get a url to the page for a specific taxonomy item. It takes two parameters: kind, which is the name of the taxonomy; and name, which is the name of the taxonomy item. So you get a link to the page for the Webdev taxonomy with: get_taxonomy_url(kind="categories", name="Webdev").

I updated the post header to this:

<header>
  <h2>{{page.title}}</h2>
  <div class="post-info">
    Date: {{page.date}}
    {% if page.taxonomies.categories %}
      | Category: <a href="{{get_taxonomy_url(kind="categories", name=page.taxonomies.categories[0])|safe}}">{{page.taxonomies.categories[0]}}</a>
    {% endif %}
  </div>
</header>

I check if there are any categories assigned to the post, and if there are I show the first one as a link to the page for that category. This design means I can only show one category per post, but I figure it would do for now.

The old hard coded category.html template is gone in 0.4, so the next step was to make templates for the categories taxonomy. Each taxonomy gets two templates now, in a folder named after the taxonomy. The two templates are:

  1. list.html. This is the template for the entire taxonomy, with access to a list of taxonomy items. You can see how it looks like for my categories taxonomy here.
  2. single.html. This is the template for a single taxonomy item, like Webdev. You can see what it looks like for Webdev here. It is also what the Webdev at the top of this post links to.

The list.html template get passed in a global variable called terms, which is a list of taxonomy items. Each item consists of the name of the item, the slug for the item, the permalink for the item and pages, a list of pages that has that taxonomy item assigned. My list.html for categories is quite simple. The main component are two for loops that loop through each taxonomy item in the taxonomy and then through each page in the taxonomy item:

<ul class="categories-list">
  {% for term in terms %}
    <li>
      <a href="{{term.permalink}}">{{term.name}}</a>
      <ul>
        {% for page in term.pages %}
          <li>
            <a href="{{page.permalink}}">{{page.title}}</a> - {{page.date}}
          </li>
        {% endfor %}
      </ul>
    </li>
  {% endfor %}
</ul>

The single.html templates gets passed a term variable, which is the taxonomy item being rendered. My template is basically just replicating my index.html template but now it loops through the pages in the taxonomy item.

Finally, I got to the reason for this entire migration: a list of categories in the sidebar. Gutenberg 0.4 adds another function to templates: get_taxonomy. It takes one argument, kind, which is the name of the taxonomy. It returns a Taxonomy object with two fields: kind, with details about the taxonomy from the config.toml file; and items, a list of taxonomy items. With this function you can access the details of a taxonomy anywhere. I updated my sidebar template like so:

{% set categories = get_taxonomy(kind="categories") %}
<h2>Categories</h2>
<ul>
  {% for category in categories.items %}
    <li>
      <a href="{{category.permalink}}">{{category.name}}</a> ({{category.pages|length}})
    </li>
  {% endfor %}
</ul>

And so, at long last, a list of categories in the sidebar!

Gutenberg short code for making columns

One of the things I like about Gutenberg (the static site generator used for this blog) is the support for short codes. Short codes are essentially tiny Tera templates you can call in the markdown files for the content. This immediately lead me to look for a way to make short codes for multiple columns of content.

My first attempts looked something like this:

{% columns() %}
{% column() %}
Content in the left column
{% end %}
{% column() %}
Content in the right column
{% end %}
{% end %}

It didn't work. Turns out the short code parser in Gutenberg doesn't support nested short codes. The reason I wanted to nest two short codes is that the essential bit in embedding some columns in the content is that you need two levels of divs: An outer div as a container for the columns, and inner divs wrapping each column.

So, I was stuck. Later, when browsing the Tera docs for an unrelated reason, I was that it has a split method. That gave me an idea. I don't really need an inner short code, I just need a way to tell apart the content for different columns. What I could do is have one short code that splits the body of the short code based on some specified string.

The use of the new columns short code looks like this:

{% columns() %}  

Content in the left column

[[gutter]]

Content in the right column

{% end %}

It splits the body into columns any time it comes across [[gutter]]. One weakness of this is that there is no way to escape [[gutter]] so you can't write about it in a column. There is also no way to specify different column sizes. The code for the short code ended up being short and sweet:

<div class="columns">
  {% set cols = body|split(pat="[[gutter]]") %}
  {% for col in cols %}
    <div class="column">
      {{col}}
    </div>
  {% endfor %}
</div>

Then all that is needed is some CSS to layout the classes. Here is an example using flexbox to be compatible with IE11:

$gutter: 1rem;
$min-viewport-size: 600px;

.columns {
  display: flex;
  flex-direction: column;
}

.column {
  margin-bottom: $gutter;
  flex-grow: 1;
  flex-basis: 0;
  &:last-child {
    margin-bottom: 0;
    margin-right: 0;
  }
}

@media (min-width: $min-viewport-size) {
  .columns {
    display: flex;
    flex-direction: row;
  }
  
  .column {
    margin-bottom: 0;
    margin-right: $gutter;
  }
}

It uses two variables:

  • $gutter: Space between column.
  • $min-col-size: Min viewport width for switching from single column to multi column mode.

If IE11 support doesn't matter, then one could, of course, do something slick with CSS grids:

.columns {
  display: grid;
  grid-gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

That is a lot less CSS to write. This one spills columns over onto the next row to avoid them being narrower than 260px. It end up looking like this:

Bacon ipsum dolor amet leberkas ham beef ribs bresaola drumstick frankfurter cow tail. Sirloin cow picanha short loin short ribs turkey prosciutto landjaeger brisket cupim tail pig strip steak. Alcatra turducken meatloaf picanha, venison chuck pork chop ham hock. Strip steak sirloin pork chop biltong. Picanha chuck jowl turkey, doner salami ground round corned beef cow tri-tip ham brisket biltong.
Burgdoggen strip steak frankfurter tenderloin capicola, sirloin ball tip andouille tri-tip rump chicken. Shankle pork belly boudin, turducken jerky andouille bacon tenderloin ham hock capicola jowl filet mignon bresaola short loin. Bacon meatball cow sausage meatloaf. Chicken capicola cow shank frankfurter.
Spare ribs alcatra short ribs doner drumstick biltong ribeye pork chop ball tip frankfurter picanha pork loin short loin tail pancetta. Picanha ground round spare ribs pork belly landjaeger burgdoggen bresaola salami. Pig hamburger strip steak short loin doner. Short ribs t-bone shankle kevin corned beef. Fatback strip steak meatloaf, short loin ground round pancetta sirloin landjaeger. Ham chuck pig andouille ball tip kielbasa cupim sirloin jowl meatloaf chicken shankle.

The Peter Principle of POVs - and why Robert Jordan was great

There is something known as the Peter principle which basically is the idea that people get promoted to their level of incompetence. When someone is competent at what they do they are rewarded by being promoted to a new position. If they are still competent at what they do in their new position they get rewarded with a promotion again. This keeps happening until they get promoted to a job they are not good at. That is when the promotions stop and that is where they stay.

There is a similar pattern that can be observer in the use of Points of Views in books, especially in epic fantasy series. Juggling multiple POVs is tricky. Handling the different plot threads and POV switches without ruining the flow and pacing of the story telling is a challenge. It seems to me like there are many fantasy authors that keep adding POVs until they can't juggle them anymore.

A prime example of this is Robert Jordan's Wheel of Time series. That is a series that by the end was drowning in POVs. It is a common criticism of the Wheel of Time series that it had too many POVs. But this post is not a complaint about how Robert Jordan had too many POVs. Rather, I want to highlight how well he did, and how many POVs he was juggling before he became overwhelmed. I will try to avoid spoilers as much as possible.

Many authors seem to struggle once they go beyond three POVs. Robert Jordan was successfully juggling fifteen in the middle of Wheel of Time. I thought I would take a moment to see what he was doing that let him juggle so many POVs so well. The Robert Jordan rules of writing POVs, if you will. To be clear, the these are my thoughts on what he did. I don't say he would agree with me.

I must start with a couple of disclaimers/clarifications:

  1. Robert Jordan dealt with POVs differently in prologues/epilogues as compared to the main chapters in the book. In the prologue/epilogue He would often drop in to some completely unknown POV in order to give the reader some piece of information in order to set something up or foreshadow something. I am going to ignore this and focus on how he dealt with POVs in the main chapters of the books.
  2. He broke all the rules I will lay out here in one instance or another, more so in latter books in the series, probably because it became harder to keep to them when the number of POVs grew so great.
  3. Due to Robert Jordan's untimely passing the final three books were finished by Brandon Sanderson. How to portion out credit/blame for those books is something I won't touch upon.

So, without further ado, let’s get started with Robert Jordan's tricks of writing POVs.

Robert Jordan used third person limited narrator in his Wheel of Time. This means that, even though the narration was in third person, it would be limited to the kind of information the POV had. He would also color descriptions based on this. Like descriptions for a blacksmith's POV would include detail about the quality of the metalworking of the tools people were using, or the solider would notice if someone's sword had been properly cleaned and maintained. While Robert Jordan had his own voice as an author, he would flavor it differently for different POVs, in descriptions, words, thoughts and deeds.

He is also not mechanical in how he switches between POVs. Some authors mechanically switch to a new POV every chapter, whether it makes sense or not. Robert Jordan would be a lot more dynamic than that. Often, he would keep to the same POV for many chapters in a row, while in other situations he would switch POV many times in a chapter. This also has an interesting effect on the tempo of the storytelling. Staying with one POV for a long time is a more leisurely pace, while constant switching would feel more hectic. He would often switch POVs more towards the climax of a book. This would partially be because of the obvious reason that various storylines were converging, but it would also give the climax more tension, make it feel like more action.

Another thing that Robert Jordan does, that helps more the more POVs and POV-switches there are is that time is strictly increasing. What I mean by that is that time in the story is always moving forward, no matter where or how the POV switches. Every event described happens after the pervious event. For POV-switches this means that whatever the next POV after the switch sees or does happens after whatever the previous POV, from before the switch, did or saw. By avoiding going backwards in time, or even sideways, where the same time point is described from two different POVs, he greatly eases the readers ability to keep separate POVs in sync. Besides helping to keep the POVs in line it also helps keep the story moving forward. There is always momentum. Sadly, this rule completely falls apart in the last three books, which were originally supposed to be one book, but got split up into three. The split ended up completely messing up the timelines. Not just does this hurt especially bad with how many POVs there were by then, but because time starts flowing at different rates in different parts of the world due to "plot-reasons". Having abandoned strict time just before time gets wonky made things a lot worse.

Robert Jordan uses strict time and POV switches in a clever way to avoid too big time jumps but at the same time to avoid dead time. Strictly increasing time doesn't prevent time jumps if the jumps are forward in time. Robert Jordan often did time jumps between books in Wheel of Time but tried to avoid very big ones in the middle of books. This can be a problem in this sort of medieval fantasy book because there is a lot of dead time while characters are travelling between location, mostly by ship or horse. Robert Jordan uses POV switches to mask this. For example: some POV heads off with a ship on a trip that will last a week. He masks this time by switching to some other POV and follow that for a week and then switch back once a week on time has passed.

Related to how Robert Jordan handles dead time is how he switches between plot threads. Some authors, when switch plots threads, leave the previous plot thread on a cliffhanger. Cliffhangers on plot thread switches in books are like jump scares in movies. They reek of desperation. A good horror movie does not need jump scares to be scare. A good book does not need cliffhangers on switches between plot threads to keep the reader hooked. Robert Jordan usually stuck with the same plot thread for several chapters in a row, only switching away when the plot thread had reached some sort of resolution or at least a pause. This made each set of chapters following a single plot thread feel like a short story.

One of the most important things Robert Jordan did to manage so many POVs was to introduce the character before the character was introduced as a POV. By having an existing POV get to know something about a new character before that character becomes a POV the reader also starts the new POV with some notion of how the new POV fits into the story. This also made plot lines branch out from one line in a tree type fashion, rather than dumping a bunch of unrelated plot lines on the reader and hoping the reader keeps them straight. Now, Robert Jordan did have different ways of introducing new soon-to-be POV characters. The most straight forward way was to have existing POV character meet and interact with a new character, and then, later, switch to that new characters POV. Another way he did it if the characters couldn't meet was to have an existing POV character hear about some new character before that new character became a POV. If he wanted to keep the new character mysterious he would have an existing character do something somewhere and then switch to a new POV watching the existing character covertly. That way there would still be some tie to an existing character to keep the branching structure in place but keep the reader in the dark about the new POV.

To help smooth over POV switches Robert Jordan does something I call bridging. By this I mean that he keeps the thread of events ongoing across POV switches by starting off new POV after the switch following on from the last event of the old POV from before the switch. Let me give an example to make it clear what I am talking about. We are following POV character A and something happens and as a result the character decides to run off to deal with it. At this point the POV switches to another character B. It starts with POV character B reflecting on having just seen character A run off and continues with what B does after that. Because of strictly increasing time after the switch to character B cannot involve B seeing A run off, because that already happened for POV A, but it can still tie in to it by B reflecting on it. This way even though the POV switches, the chain of events remains unbroken, by having the new POV start of from where the previous POV left of. Robert Jordan mainly used this trick in two sorts of situations. One was when he did a lot of POV switches in one chapter between different POVs involved in the same events. The other is when switching to a new, never before seen, POV. Have some existing POV character meet a new character and interact with them. Then switch to new character's POV with the new character leaving the meeting and reflecting on it.

These are not the only tricks Robert Jordan used to handle his many POVs in Wheel of Time, but these are the ones that stood out to me. It is also not like it is required to use tricks like these for handling multiple POVs in books, at least not when the number of POVs are limited. Once the number of POVs start increasing beyond three to four something extra becomes useful to help the reader handle them.

The Wheel of Time series is remembered for how it drowned in POVs, which is a shame. It deserves to be remembered for how well it handled multiple POVs.

Raven's shadow - To fall so far

Raven's Shadow is the first series by a new author Anthony Ryan. It’s a trilogy consisting of Blood Song, Tower Lord and Queen of Fire. It’s also a series which starts so great and end in such mediocrity.

The first book is really one of the great works of Fantasy. The book is really great. Its set in a medieval fantasy world and is a single POV story following the protagonist Vaelin Al Sorna. He gets handed to a sort of templar like warring monk society as a child and the book covers his time growing up among the warrior monks and then how he ends up in the service of the king of the land. Being fantasy there is also some magic in play. Vaelin has a sort of magical gift, the titular Blood Song, which guides him, at least when he bothers to listen.

The book also has a brilliant narrative setup where is sort of has two narrators. The prologue and epilogue is told from the point of view of a historian how follows a condemned criminal to his execution. On the way there he convinces the criminal to tell him his life story, so he can record it. The main narrative of the book is then the story of the criminal, Vaelin. When I started the book, I was a little worried about how well this would work in that the prologue sort of spoils the main story, but my worried were unfounded. In the end the meta-narrative of the historian and the life story of Vaelin come together beautifully. The ending is truly epic.

After such a great start the second book, Tower Lord, is a big step down. This is not to say the say the second book is a bad book. It’s not. Rather it’s just that the first one is so great that you can have a big step down and still end up with a good book. Rather than being a single POV book like the first one, this one has multiple POVs. It also handles them in a way I just loath: Jump between POVs and storylines every single damn chapter. It seems like it’s become quite popular lately. I blame George RR Marten. The good there is in A Song of Ice and Fire is good despite the constant jumping not because of it.

The book also undermines the previous book's world building. Where Vaelin had to train from childhood to achieve his martial prowess, now there are characters popping up that are way too good for what training they have. Worst offender here is a new female character called Reva. Which is a shame, for aside from that she is the most interesting new character, and the only one with a proper character arc.

If there was one weakness in the first book it was that the romance was kind flat and passionless. In the second book there is a new magically induced romance. Another flat and passionless romance, this time with a side dish of questionable consent. That does not improve things.

Where the second book was still a good book, the third one, Queen of Fire, isn't even that. It is decidedly mediocre. There will be some minor spoilers ahead, but this book isn't worth reading anyway, so don't worry. It is time for the main characters to take the war to the Evil Empire. This war is really boring with short descriptions of uninteresting battles against mind-controller slave soldiers. They are like the worst evil overlord goons.

To spice things up Vaelin loses his blood song ability. Cliched sure but could be interesting. How does the hero deal with losing his defining super power? But after moping about it a bit early in the book he runs into a woman with the same ability, so he just spends the rest of the book following her around instead. So, the entire plot point really doesn't lead to any interesting character growth or anything. He can just spend the rest of the book ignoring the issue. So, what does he do? Spends most of the book traipsing across a glacier just to get a big exposition dump in the end that explain everything about the big baddie. Who turn out to be boring.

The most interesting character in the last book is Reva, and that's saying something considering that she has neither a character arc nor anything to do with the plot. Reva's character arc is basically complete by the end of the second book. There is some hope for something interesting when she adopts a girl early in the book, but after that the girl is never heard from again and has no bearing on the plot. She also has her own side plot that has nothing to do with the resolution of the main plot. She just sort of ends up at the same place as all the other characters in the end. But at least her sub-plot has interesting action and adventure, not just dry battle description or walking across a glacier.

The ending still leaves a bunch of threads hanging. Like the how Vaelin never deals with losing the blood song. Reva's hunt for the priest from her childhood never goes anywhere. The wolf god thing is never explained. And the prophecy from the first book? Apparently just someone's pipe dream or something because it never leads anywhere.

The last book is also the longest, and it really feels like it. Plot-wise, you could just rip out both Reva and Vaelin, and just have someone else get the boring exposition dumps. That way you could at least cut the length of the book in half to tighten up the pacing. But honestly, it would still be boring.

My recommendation is to read the first book, because it is really awesome. Then just skip the rest. Sure, that leaves a few plot threads hanging, but their resolution is boring and the final book has its own unresolved plot threads anyway, and the ending of the first book is way better than the end of the trilogy.