Writing Resilient Components
Four principles to set you on the right path.
Writing Resilient Components
March 16, 2019
When people start learning React, they often ask for a style guide. While it’s a good idea to have some consistent rules applied across a project, a lot of them are arbitrary — and so React doesn’t have a strong opinion about them.
You can use different type systems, prefer function declarations or arrow functions, sort your props in alphabetical order or in an order you find pleasing.
This flexibility allows integrating React into projects with existing conventions. But it also invites endless debates.
There are important design principles that every component should strive to follow. But I don’t think style guides capture those principles well. We’ll talk about style guides first, and then look at the principles that really are useful.
Don’t Get Distracted by Imaginary Problems
Before we talk about component design principles, I want to say a few words about style guides. This isn’t a popular opinion but someone needs to say it!
In the JavaScript community, there are a few strict opinionated style guides enforced by a linter. My personal observation is that they tend to create more friction than they’re worth. I can’t count how many times somebody showed me some absolutely valid code and said “React complains about this”, but it was their lint config complaining! This leads to three issues:
People get used to seeing the linter as an overzealous noisy gatekeeper rather than a helpful tool. Useful warnings are drowned out by a sea of style nits. As a result, people don’t scan the linter messages while debugging, and miss helpful tips. Additionally, people who are less used to writing JavaScript (for example, designers) have a harder time working with the code.
People don’t learn to differentiate between valid and invalid uses of a certain pattern. For example, there is a popular rule that forbids calling setState inside componentDidMount. But if it was always “bad”, React simply wouldn’t allow it! There is a legitimate use case for it, and that is to measure the DOM node layout — e.g. to position a tooltip. I’ve seen people “work around” this rule by adding a setTimeout which completely misses the point.
Eventually, people adopt the “enforcer mindset” and get opinionated about things that don’t bring a meaningful difference but are easy to scan for in the code. “You used a function declaration, but our project uses arrow functions.” Whenever I have a strong feeling about enforcing a rule like this, looking deeper reveals that I invested emotional effort into this rule — and struggle to let it go. It lulls me into a false sense of accomplishment without improving my code.
Am I saying that we should stop linting? Not at all!
With a good config, a linter is a great tool to catch bugs before they happen. It’s focusing on the style too much that turns it into a distraction.
Marie Kondo Your Lint Config
Here’s what I suggest you to do on Monday. Gather your team for half an hour, go through every lint rule enabled in your project’s config, and ask yourself: “Has this rule ever helped us catch a bug?” If not, turn it off. (You can also start from a clean slate with eslint-config-react-app which has no styling rules.)
At the very least, your team should have a process for removing rules that cause friction. Don’t assume that whatever you or something somebody else added to your lint config a year ago is a “best practice”. Question it and look for answers. Don’t let anyone tell you you’re not smart enough to pick your lint rules.
But what about formatting? Use Prettier and forget about the “style nits”. You don’t need a tool to shout at you for putting an extra space if another tool can fix it for you. Use the linter to find bugs, not enforcing the a e s t h e t i c s.
Of course, there are aspects of the coding style that aren’t directly related to formatting but can still be annoying when inconsistent across the project.
However, many of them are too subtle to catch with a lint rule anyway. This is why it’s important to build trust between the team members, and to share useful learnings in the form of a wiki page or a short design guide.
Not everything is worth automating! The insights gained from actually reading the rationale in such a guide can be more valuable than following the “rules”.
But if following a strict style guide is a distraction, what’s actually important?
That’s the topic of this post.
Writing Resilient Components
No amount of indentation or sorting imports alphabetically can fix a broken design. So instead of focusing on how some code looks, I will focus on how it works. There’s a few component design principles that I find very helpful:
- Don’t stop the data flow
- Always be ready to render
- No component is a singleton
- Keep the local state isolated
Even if you don’t use React, you’ll likely discover the same principles by trial and error for any UI component model with unidirectional data flow.
Principle 1: Don’t Stop the Data Flow
Don’t Stop the Data Flow in Rendering
When somebody uses your component, they expect that they can pass different props to it over time, and that the component will reflect those changes:
// isOk might be driven by state and can change at any time
<Button color={isOk ? 'blue' : 'red'} />
In general, this is how React works by default. If you use a color prop inside a Button component, you’ll see the value provided from above for that render:
function Button({ color, children }) {
return (
// ✅ `color` is always fresh!
<button className={'Button-' + color}>
{children}
</button>
However, a common mistake when learning React is to copy props into state:
class Button extends React.Component {
state = {
color: this.props.color
render() {
const { color } = this.state; // 🔴 `color` is stale!
return (
<button className={'Button-' + color}>
{this.props.children}
</button>
This might seem more intuitive at first if you used classes outside of React. However, by copying a prop into state you’re ignoring all updates to it.
// 🔴 No longer works for updates with the above implementation
<Button color={isOk ? 'blue' : 'red'} />
In the rare case that this behavior is intentional, make sure to call that prop initialColor or defaultColor to clarify that changes to it are ignored.
But usually you’ll want to read the props directly in your component and avoid copying props (or anything computed from the props) into state:
function Button({ color, children }) {
return (
// ✅ `color` is always fresh!
<button className={'Button-' + color}>
{children}
</button>
Computed values are another reason people sometimes attempt to copy props into state. For example, imagine that we determined the button text color based on an expensive computation with background color as an argument:
class Button extends React.Component {
state = {
textColor: slowlyCalculateTextColor(this.props.color)
render() {
return (
<button className={
'Button-' + this.props.color +
' Button-text-' + this.state.textColor // 🔴 Stale on `color` prop updates
{this.props.children}
</button>
This component is buggy because it doesn’t recalculate this.state.textColor on the color prop change. The easiest fix would be to move the textColor calculation into the render method, and make this a PureComponent:
[...]