Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ryanflorence/a301dc184f75e929a263dc1e80399a28 to your computer and use it in GitHub Desktop.
Save ryanflorence/a301dc184f75e929a263dc1e80399a28 to your computer and use it in GitHub Desktop.
Performance Conditionally Rendered Content in React

Performance and Conditionally Rendered Content in React

First, this is not about if in JSX. It's just the simplest example to talk about (and a lot of people tried to do it at first a long time ago).

Some react components conditionally render content. When React first went public, a lot of us coming from handlebars really wanted "if" syntax. This gist isn't just about If components though, it's about any component that has an API that conditionally renders stuff.

{{#if stuff}}
  <div>Thing</div>
{{/if}}

And so we made stuff like this:

<If cond={stuff}>
  <div>Thing<Div>
</If>

And then <If> was implemented like so:

const If = ({ cond, children }) => (
  cond ? children : null
)

Works great, but there's a performance issue, every render you are calling createElement on the div. Remember, JSX transpiles to this:

React.createElement(
  If,
  { cond: stuff },
  React.createElement(
    'div',
    null,
    'Thing'
  )
)

So every render we're calling createElement('div') even though it never gets rendered. For small bits of UI, this isn't really a problem, but it's common for a large portion of your app to be hiding conditionally behind that If.

So, when you've got a component that conditionally renders some of the children it was passed, consider using a render callback instead:

<If cond={stuff}>
  {() => (
    <div>Thing</div>
  )}
</If>

And then If looks like this:

const If = ({ cond, children }) => (
  cond ?  children() : null // called as a function now
)

This is good because now we aren't calling createElement('div') unless it's actually rendered.

Again, not a big deal in small cases, but for something like React Router Match or React Media, your entire app may live inside of a <Media> or <Match> component, and you calling createElement every render of your entire app that isn't actually rendered would cause performance issues.

So, if you conditionally render content, consider using a render callback.

@ryanflorence
Copy link
Author

Why not? The bad side-effect of the original <If> is gone.

@zanzamar
Copy link

Here is a pretty cool babel plugin we use:
https://www.npmjs.com/package/babel-plugin-jsx-display-if

@ryanflorence
Copy link
Author

This isn't about if! 😆

@gartz
Copy link

gartz commented Dec 9, 2016

sorry to say, your analysis on how React works is wrong.

Using a conditional: https://jsfiddle.net/69z2wepo/64786/
Using a function: https://jsfiddle.net/69z2wepo/64787/

As you can see in the tests, constructor and render will be triggered the exactly same number of times on both approaches.

@ryanflorence
Copy link
Author

ryanflorence commented Jan 11, 2017

@gartz I'm not talking about construction or render, it's about calls to React.createElement(Foo). If you monkey-patch React.createElement to increment a createElementTimes in your examples you'll see a difference.

On a personal note, don't be so quick to tell people their understanding of something is wrong :P

@gartz
Copy link

gartz commented Jan 11, 2017

@ryanflorence you should follow your own advice and take time to read source code or at least create tests to prove your theories, you are still wrong, I will tell you why.

There is a good reason why React.createElement is called multiple times and it really doesn't make much difference, go ahead and read the source code from it, you might learn something or prove that I'm wrong and teach me something.

What you're recommending here is a micro-optimization that hurts the syntax for no real performance gains, extending object properties is a very cheap operation and the heavy operations are in the Constructor and Render of components, what I've already proved your solution doesn't change it.

@ryanflorence
Copy link
Author

ryanflorence commented Jan 11, 2017

In the case of React Router, your entire application would have createElement being called. That could easily become 100,000, I'd even guess 1,000,000 in some of the apps I've worked on.

Why are you so grumpy about this?

@gartz
Copy link

gartz commented Jan 11, 2017

This might change in the future by React team, until there, here is their code: https://twitter.com/ryanflorence/status/819266202455265280

Your tests get slow because you're doing in NODE_ENV !== 'production', and with the debug console open JIT won't apply optimizations on that code, what speeds up that even more.

About number of elements, 1000 elements it's a reasonable number of elements for a regular website. Those numbers you're basing your theory are edge cases.

This page have ~1k elements, a twitter dashboard have ~5k elements, facebook initial page have ~5k elements. A page with 100k to 1m elements need way more optimization that your solution.

I'm very sorry if the way I talk to you make you feel uncomfortable, look for the bright side, you might learn something here and help to drive the community to do better design choices, since your React-Router is that important.

@gartz
Copy link

gartz commented Jan 11, 2017

The link went to the tweet, here is the link of the gist: https://gist.github.com/gartz/54d21607b443522647b6f5885e8ba56d

@BrodaNoel
Copy link

@Abee007
Copy link

Abee007 commented Dec 30, 2023

I'm very late to the conversation; however @ryanflorence's callback approach appears to yield a more ergonomic developer experience with regard to code correctness. I also think @gartz's re-render argument holds a lot of water. To demonstrate my argument, consider the following:

stuff = { key: "Thing" }
<If cond={!!stuff}>
  <div>{stuff.key}<div>
</If>

With the original approach, this code block will work as expected for the most part, but say stuff becomes null all of a sudden, what happens then? Your program will error out (cannot access key of undefined) because the children of <If> are its function arguments and all function arguments are evaluated when passed in. Devs may therefore end up in a situation where their code fails unexpectedly. On the other hand, because the result of callbacks are not evaluated when passed into functions as arguments, implementing <If>using the second approach is much safer.

stuff = { key: "Thing" }
<If cond={!!stuff}>
  {() => <div>{stuff.key}<div>}
</If>

Something like the above should not crash your program arbitrarily, and holds the expectation that devs have: that the children of the <If> block are only evaluated when cond is true. I'd love to hear what y'all think though ¨̮

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment