I lead the engineering team responsible for building our company’s design system and component library. Our components are not only seen by millions of users, they’re used by my fellow developers across dozens of repositories. After building, supporting, updating, bug fixing, and deprecating many React components with a wide audience, I’ve learned a ton of lessons and distilled my experiences into a few best practices I try to follow.
These best-practices don’t just apply to React front-end development; in fact, they’re common across software development and even engineering in general. They’re not ground-breaking, and I doubt you’ll find them shocking. But it is amazing how much of a difference keeping these simple guidelines in mind can actually make.
If you add these to your tech-design process and PR check-list, your teammates, consumers, and future self will thank you.
It all boils down to this: Simplify your API.
When I’m building a React component, my primary audience is the developer who will be using it. To give them the best experience, I need to minimize complexity. That means as I weigh all the possible ways to build a component, simplicity in the API is the most important factor.
A simple API is not the same as simple code. In fact, creating a simple API for our consumers often leads to increased complexity within our codebase, as we add additional logic behind the scenes. That’s a tradeoff we have to weigh during our tech design process.
What does it look like to create a simple API? There can be a lot of factors involved, but for React components I primarily look at:
- Minimizing the total lines of code a developer has to write
- Minimizing the average number of props that are used
- Simplifying the types and depth of my props — i.e. using strings and numbers over objects and arrays
- Matching standard HTML prop naming conventions
- Eliminate edge cases with strong typing
Let’s dig into each of these.
Minimize total lines of code
This should be pretty straight-forward. In React, less code often means less complexity, although that isn’t universally true in programming. I have seen some cases where it takes 10+ lines of code to fill out the props for a simple button component, which makes it difficult and time-consuming to build even simple flows.
To help with this, I suggest avoiding redundancy and providing default values. Never use 2 props when 1 prop will do. Similarly, never use 1 prop when 0 props will do. If one prop’s value can be inferred from another, build that logic into your component. Hide the complexity behind the scenes so that your consumer doesn’t have to think about it.
Minimize the average number of props
My team builds components that are highly versatile and flexible. There are often many different ways to configure a given component, but there is one way that is the most common. I optimize for this most-common case.
Set up default behaviors and prop values so that most of the time, a developer won’t need to think about that prop at all. The holy grail is when a component can be used with no props—and it still does exactly what they need it to do.
Simplifying type and depth of props
One tempting approach to minimize the number of props is by cramming all the less common options into some catch-all prop that accepts an object. I have found that this doesn’t scale. Accepting object
s for props is an anti-pattern on our team, because it’s difficult to document well and almost always creates more complexity.
I recommend using string
and number
types for props and moving all complexity to the children
prop. There’s often a way to expose subcomponents with configuration of their own instead of accepting object
or Array
s as props. It makes consumer’s code simpler to read and understand and gives them more flexibility and control.
Match HTML prop naming conventions
Naming is important in programming. With higher-quality names, we can spend less time consulting documentation. In React, we’re already working with common HTML elements that have standardized names and types. If my API matches the HTML standard, there’s less for me to document. Where it deviates, I have to document.
Our team uses object destructuring and spreading ({ ...props}
) extensively to pass through standard HTML props to the component. For event handlers, we use the “on” prefix. We use “disabled” and “checked” props to match the standards on input
elements. Any time we can avoid introducing a new prop name, we keep our API surface area that much smaller.
Eliminate edge cases
Even in a simple component with just a few props, the total permutations of configuration can quickly get out of hand. My goal is to avoid the permutations that don’t make sense—for example, when “readonly” and “disabled” are set on a Button, or when “selected” is used on a non-selectable variant of Card.
We take two approaches to making these situations impossible: first, consolidating conflicting props. If “readonly”, “disabled”, and “loading” are all conflicting props on my Button, why not move them into a single “state” prop? Otherwise, when consolidation isn’t possible, we lean on TypeScript and other tooling to catch those configurations at build time.
Keep these guidelines in mind as you update and build new React components and watch how easy it is for other developers to use your components!
Hope you enjoyed!