Skip to content

Instantly share code, notes, and snippets.

@nemoDreamer nemoDreamer/README.md
Last active May 10, 2018

Embed
What would you like to do?
`React.cloneElement` vs. `Render Prop` pattern benchmarking

React.cloneElement vs. Render Prop pattern benchmarking

Run:

babel clone-element-vs-render-prop.jsx | node

Result:

  • React.cloneElement x 41,818 ops/sec ±0.76% (1079 runs sampled)
  • Render Prop x 47,911 ops/sec ±0.59% (1079 runs sampled)

Fastest is Render Prop

Conclusion:

  • The margin of performance improvement is negligible for "Render Prop". You have to manually spread the props, and for the nested pattern to work, you also have to pass the onus of nesting children to the Container...
  • The "React.cloneElement" solution is more legible, and keeps the nested structure requirement inside the Presentation where they belong.

I propose sticking with "React.cloneElement", unless I've missed something and the "nesting" problem for "Render Prop" can be solved more elegantly?

Correction:

Update: see correction coment below.

import React from 'react';
import PropTypes from 'prop-types';
import TestRenderer from 'react-test-renderer';
const Benchmark = require('benchmark');
Benchmark.options.minSamples = 1000;
const suite = new Benchmark.Suite();
// --------------------------------------------------
// PROPS
// --------------------------------------------------
const outerProps = {
style: {
color: 'black',
},
id: 'outer',
};
const innerProps = {
id: 'inner',
};
// --------------------------------------------------
// PRESENTATIONS
// --------------------------------------------------
const PresentationCloneElement = ({ outerElement, innerElement }) => (
<div>
{React.cloneElement(
outerElement,
outerProps,
React.cloneElement(innerElement, innerProps),
)}
</div>
);
PresentationCloneElement.propTypes = {
outerElement: PropTypes.node.isRequired,
innerElement: PropTypes.node.isRequired,
};
const PresentationRenderProp = ({ outerRender, innerRender }) => (
<div>
{outerRender({
...outerProps,
children: innerRender(innerProps),
})}
</div>
);
PresentationRenderProp.propTypes = {
outerRender: PropTypes.func.isRequired,
innerRender: PropTypes.func.isRequired,
};
// --------------------------------------------------
// CONTAINERS
// --------------------------------------------------
const ContainerCloneElement = ({ isDisabled, label }) => (
<PresentationCloneElement
outerElement={<button disabled={isDisabled} />}
innerElement={<span>{label}</span>}
/>
);
ContainerCloneElement.propTypes = {
isDisabled: PropTypes.bool.isRequired,
label: PropTypes.string.isRequired,
};
const ContainerRenderProp = ({ isDisabled, label }) => (
<PresentationRenderProp
outerRender={({ children, ...props }) => (
<button disabled={isDisabled} {...props}>
{children}
</button>
)}
innerRender={props => <span {...props}>{label}</span>}
/>
);
ContainerRenderProp.propTypes = {
isDisabled: PropTypes.bool.isRequired,
label: PropTypes.string.isRequired,
};
// --------------------------------------------------
// TESTS
// --------------------------------------------------
const testCloneElement = () =>
TestRenderer.create(<ContainerCloneElement isDisabled label="Yoyo" />);
const testRenderProp = () =>
TestRenderer.create(<ContainerRenderProp isDisabled label="Yoyo" />);
// these should have identical output:
console.log(testCloneElement().toJSON());
console.log(testRenderProp().toJSON());
// --------------------------------------------------
// SUITE
// --------------------------------------------------
suite
.add('React.cloneElement', () => {
testCloneElement();
})
.add('Render Prop', () => {
testRenderProp();
})
// add listeners
.on('cycle', event => {
console.log(String(event.target));
})
.on(
'complete',
// eslint-disable-next-line func-names
function() {
console.log(`Fastest is ${this.filter('fastest').map('name')}`);
},
)
// run async
.run({ async: true });
@nemoDreamer

This comment has been minimized.

Copy link
Owner Author

nemoDreamer commented Apr 10, 2018

Correction:

The "Render Prop"'s "nesting" problem can actually be avoided:

const PresentationRenderProp = ({ outerRender, innerRender }) => (
  <div id="outerWrapper">
    {outerRender({
      ...outerProps,
      children: <div id="innerWrapper">{innerRender(innerProps)}</div>,
    })}
  </div>
);

const ContainerRenderProp = ({ isDisabled, label }) => (
  <PresentationRenderProp
    outerRender={props => <button disabled={isDisabled} {...props} />}
    innerRender={props => <span {...props}>{label}</span>}
  />
);

But needing to manually spread props is still an issue, but it does offer the flexibility of being able to render a deeper structure, and pepper the props at different levels.

So all-in-all, with that strong benefit, and the new legibility of "Render Prop", I'd propose going with that pattern...!

@PatrickChevalierADSK

This comment has been minimized.

Copy link

PatrickChevalierADSK commented Apr 10, 2018

Good stuff! And also it's the fastest! 🎆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.