All Articles

TIL: Correct way to compare react children

Image of React logo with next to a chunk of react code

We recently had a snippet of code in our React app that looked like this:

let menuContent = null;
let progressBar = null;

if (React.Children.count(children) > 1) {
  React.Children.forEach(children, (child) => {
    if (child.type.name === 'ProgressBar') {
      progressBar = child;
    }
    else {
      menuContent = child;
    }
  });
}
else {
  menuContent = children;
}

The idea behind this code was to populate the content of our menubar with the menuContent and the progressBar (if available). This piece of code works perfectly, however we noticed that in production we only had the menuContent showing up, but the progressBar did not.

I was left wondering why we could have something work locally but behave differently in production. It was a bit mind boggling because we have processes to make sure we have closing matching dev and prod environments.

I went ahead to try debugging using the react dev tools extension to inspect the component tree and take a look at the props. I did this for the production environment while also comparing the components to the on on my local environment.

What I found out was that the progressBar was not rendering because it was null.

I could see that the parent component (in both local and production environments) had exactly 2 children. This meant that the component was supposed to render given that the first if statement was run rather than the else statement. So:

let menuContent = null;
let progressBar = null;
if (React.Children.count(children) > 1) {
  // This should run given that children is greater than 1
  // ...
}
else {
  menuContent = children;
}

So what gives? It took me a bit of further debugging and also using the tricks for Debugging React Apps in Production to figure out what the issue was.

The issue was in the nested if statement:

  if (child.type.name === 'ProgressBar') {
      progressBar = child;
  }
  else {
      menuContent = child;
  }

We were checking against a “hardcoded” value of 'ProgressBar' and comparing with child.type.name. On first glance this doesn’t seem to be an issue, but in a production build, the react component names are changed on minification (as a result of the build step). This means that even though in the local environment the child.type.name is 'ProgressBar', in the production build it could be a random string like 'Rs' or whatever the bundler choses to minify it to.

Though we had to rethink the approach and update the behaviour of this component, the way to correctly compare react component element names is to compare the name key on the elements rather than using strings. Something like:

import {ProgressBar} from 'path/to/progressBar';

// Then afterwards...
if (child.type.name === ProgressBar.name) {
    progressBar = child;
}
else {
    menuContent = child;
}