In the early years, react was marketed and was well-renowned for its rendering performance.
Nowadays, most core concepts have been adapted by the competition, and react is no longer the absolute forefront, especially if the boot up time is considered. The combination of react and react-dom is heavy.
Delivering fast web applications with react is totally possible, but here’s the but: Doing so requires considerable knowledge on how react works internally, and constant vigilance.
A month back for instance, I pushed a small feature-addition to an existing component. The code looked like this:
There was a container component index.js
:
import { connect } from 'react-redux'
import { selectSomeObjectFromState } from 'data/selectors'
/* ...a few more reselect selectors... */
import MyComponent from './MyComponent'
const mapStateToProps = (state) => ({
myArray: selectSomeObjectFromState(state).myArray || [],
/* ...more props selected from redux state... */
})
export default connect(mapStateToProps)(MyComponent)
And then the actual component MyComponent.js
:
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import styles from './MyComponent.css'
const MyComponent = ({ myArray /* ...those other props... */ }) => (
<div className={styles.MyComponent}>{/* render some stuff */}</div>
)
MyComponent.propTypes = {
myArray: PropTypes.array.isRequired,
/* the other props */
}
export default memo(MyComponent)
Let’s recap what we saw:
- First, there was the container component that consumes some object from the redux state, and passes an attribute to the component. If the value is set falsy in the redux state, an empty array is passed.
- The component receives quite a few props, but the rendering is so straightforward that a functional component was the obvious choice. I knew that the component re-renders a couple of times, which is why I wrapped its export with
React.memo
.
Can you spot the mistake?
Of course 💡: Every time that prop myArray
is falsy in the redux state, a new array is passed to the component. The shallow equality check of memo
detects a change. The result is that MyComponent
re-renders even when there is no change in props. I was considerate enough to try to prevent unnecessary re-rendering with memo
, and accidentally caused the opposite.
Looking at this code in isolation it’s easy to spot the error, and easy to fix:
- I could write
MyComponent
in a way that it could deal withmyArray
being passed with falsy values. - I could use
propTypes
with a default prop definition, which formyArray
would define an empty array.propTypes
does not instantiate a new array for every render call. - I could change the
reselect
selector. Selectors are memoized by default. The selector could return an empty array instead of a falsy return value.
But maybe, you share my take:
A UI library should nudge its users into the direction of writing performant code. It should warn me, when I’m sloppy. And, ideally, slow code would be just a tad less subtle.