November 07, 2022

JSX was a Mistake

edit

It wasn’t. I just wanted your attention. Thanks.

And now while I have it, I do want to discuss a bit of common confusion caused by JSX.

One of the most common misunderstandings I run into with people regarding React is this: If a component has a state change and rerenders, it does not cause children to rerender. I think this is very obvious when we remove JSX, and use React.createElement instead. Let’s have a look.

Here we have one of my favorite components to demonstrate rerenders, a Box that changes background color whenever it’s rendered. This Box allows us to pass children through it. Thus, we’ll make an Example where we force an update, which will cause Box to rerender.

import { useForceUpdate } from './hooks'
import { randomRGB } from './utils'

function Box({ children }) {
  return (
    <div style={{ backgroundColor: randomRGB(), padding: '1rem' }}>
      {children}
    </div>
  )
}

function Example() {
  const forceUpdate = useForceUpdate()

  return (
    <Box>
      <button type="button" onClick={forceUpdate}>
        Update
      </button>
    </Box>
  )
}

As you can see, clicking “Update” causes the Box to change colors. This is because Box is a function that gets called when Example is called. This is obvious when instead of JSX, we use React.createElement instead.

function Box({ children }) {
  return React.createElement('div', {
    style: { backgroundColor: randomRGB(), padding: '1rem' },
    children,
  })
}

function Example() {
  const forceUpdate = useForceUpdate()

  return React.createElement(
    Box,
    null,
    React.createElement('button', { type: 'button', onClick: forceUpdate }),
  )
}

Notice that this time, we replaced <div /> with React.createElement('div'). Then when we needed to render the Box in Example, we pass it to another call of React.createElement. Thus, each time Example rerenders, we call a function to render a Box and a function to render a button. Makes it pretty clear where functions are getting called.

How does this relate to children?

Well, simply put, children is typically not a function call, it’s just a value.

What if I add a “slot” for children in our Example component like this:

function Example({ children }) {
  const forceUpdate = useForceUpdate()

  return React.createElement(
    Box,
    null,
    // createElement's third parameter is a rest parameter,
    // so we can add more items by adding more arguments
    React.createElement('button', { type: 'button', onClick: forceUpdate }),
    // here's the slot, it'll come after our button
    children,
  )
}

Now, what happens if we pass Box as children of our Example?

<Example>
  <Box>I am a child</Box>
<Example>
I am a child

Notice that our child Box doesn’t rerender, while the parent Box still does. It remains the same background color it was when it initially rendered, because it’s not getting called again when Example rerenders.

Heck, let’s have some fun and put Example into Example.

Kind of fun, isn’t it?

When does this matter?

In most cases, I wouldn’t fret about rerendering a Box or whatever component you might be working with, but it’s just good knowledge to have. You’ll be better off having this concept etched into your brain than not.

The place I’m most concerned about getting this right is the use of Context Providers. I wrote a very similar blog post all about that. Check it out if you’re interested.

In general, if you have a component with frequent state changes and it uses children, consider letting as much of the inner contents be composed via children as possible to avoid unnecessary rendering.

You never know, you might just find your component is now a bit more reusable, too.


Liked the post?
Give the author a dopamine boost with a few "beard strokes". Click the beard up to 50 times to show your appreciation.
Need help with your software problems?

My team and I are ready to help you. Hire Agathist to build your next great project or to improve one of your existing ones.

Get in touch
Kyle Shevlin's face, which is mostly a beard with eyes

Kyle Shevlin is the founder & lead software engineer of Agathist, a software development firm with a mission to build good software with good people.

Agathist
Good software by good people.
Visit https://agath.ist to learn more
Sign up for my newsletter
Let's chat some more about TypeScript, React, and frontend web development. Unsubscribe at any time.
Logo for Array.reduce()
Array.reduce()
Check out my courses!
If you enjoy my posts, you might enjoy my courses, too. Click the button to view the course or go to Courses for more information.