You Can Spread Props In JSX, But Should You?

When I first learned about spreading props in JSX, I was thrilled! It is just so convenient to pass props with <MyComponent {...this.props} />, and override props defined after the spread, like <MyComponent {...this.props} text='override text prop' />. I knew it made for a great developer experience, but I always wondered if it came with a cost. How does React handle it? Does it affect performance?

Well, it took me far too long, but I’ve finally answered my questions. You’re welcome to play around with this code using Babel’s online repl.

Transpiling Explicit Props

Let’s start with our control.

Cool, this shows how JSX gets converted to React.createElement() and made into valid javascript. Also note that props are converted and passed as an inline object literal.

Transpiling Spread Props

Given the above, how do you expect OnlySpread to be transpiled?

Oooh, nice! It just uses the already created object as its props. No cloning, just passing by reference.

Transpiling Spread AND Explicit Props

Given the above, how do you expect SpreadAndExplicit to be transpiled?

Bummer! No magic here. It just converts the explicit props to an object literal, then uses Object.assign() to merge the two.

Object.assign() is a micro-perf hit compared to creating an object literal, as this esbench shows. So, it is better to explicitly pass all props and not use a spread at all.

We’re back to a single object literal. Micro-perf win!

Transpiling Multiple Spread Props

To be pedantic, lets see what happens when we spread two objects.

Again, no magic. Not surprised that it still uses Object.assign().

More to Consider

From our exploration, we could conclude that JSX spreads are good if and only if they are not accompanied with other props.

But there is more to consider.

You see, spread is a deoptimization for two babel transforms used on production bundles: transform-react-inline-elements and transform-react-constant-elements. I want to say this can be fixed by ordering Babel’s plugins properly, but this thread explains that an inline object literal (not an object reference) is required for the optimization. Even if transform-react-inline-elements runs after <div {...someProps} /> gets converted to React.createElement('div', someProps), it will not inline it. Why? Because someProps can contain a ref, which this transform can not optimize for. Even if the referenced object does not currently contain a ref, there is no way to guarantee that it won't have one in the future.

So, Should You Spread Props In JSX?

If you prioritize developer experience over performance, then go for it. Otherwise, avoid it where you can.

Maybe Prepack will enable performant spreads in the future?

Originally publish in the react-playbook