8 Tips for Building Awesome Reusable React Components

Tips for building reusable and shareable React components.

Type-Checking Reusable Components

Type-checking is essential for a positive dev experience and an optimized code collaboration. It is a way to make code much more intelligible to others, as well as to your future-self.

1. Use TypeScript — avoid prop-types

TypeScript and prop-types are currently two of the most popular ways to type-check React code. The former validates types at compile-time, whereas the latter performs at runtime.

As the two function differently and serve quite different purposes, the case could be made that they actually do not compete with each other.

That is true — in theory.

In the real world, TS and prop-types have considerable overlap in the way they’re used. That is why you would rarely see them both used on the same project. Writing fool-proof and comprehensible code is great but that is still secondary to actually delivering a good product — and delivering fast.

As TypeScript offers much more than simple type-checking (better code suggestions in supported IDEs, etc.) and as it is increasingly becoming the standard for web development, it seems like the right pick for React, especially for reusable components.

Export & import shared components as source-code to enjoy the awesome dev experience delivered by TypeScript.

When sharing reusable components using tools like Bit.dev, components can be published and imported to any repo/codebase as source-code — making all the great benefits of TypeScript available to consumers and/or maintainers of these components.

Example: browsing through shared components in bit.dev

Having your docs auto-generated using react-docgen/ bit.dev is another major benefit of TS (in that case, a benefit shared with prop-types).

For example, this —

produces that (on a component page):

Documentation on the component page

2. Write Your TS in a Way that Enables Auto Documentation

When using react-docgen/bit.dev to auto-generate docs for your reusable components, the function’s arguments should be typed directly ( (props: PropsType) => {} ).

Generic types used to define a function ( React.FC<T> ) will not be parsed by doc-gen and will not appear in your docs.

3. Use Inherited Prop Types for Native-HTML-like Components

A better way to define a React component that behaves like a native HTML element would be to extend its type with one of React’s HTML element types.

Typing a React component as an extension of a native HTML element should not be done if a component does not receive other props (the native element’s attributes). Use the spread syntax to make sure your component also functions as a native HTML element.

In the above snippet, The ‘Button’ component returns an HTML button.

This button should be able to receive various button attributes, many of them are not explicitly mentioned as part of the props’ types. There’s (usually) no reason to prohibit others from treating that component as if it were a native HTML button.

For example, let’s say you didn’t mention disabled: boolean as part of the Button’s prop types, and someone tried to use your component with the disabled prop, expecting that to work since that is a button. TS will return the following error:

“Property ‘disabled’ does not exist on type ‘IntrinsicAttributes & IButtonProps & { children?: ReactNode; }”

Structuring directories

4. One Directory For Each Component

Place all the component’s related files under the same directory. That should also include child components that are never used independently of their parent component (in that case, place the child component in a sub-directory).

Placing all files under the same directory is yet another way to make your code more comprehensible to other developers (maintainers and consumers of your reusable components). It’s much easier to understand how different files relate to each other when they’re grouped under the same directory. Moreover, it’s a way to make your reusable component more ‘mobile’ and autonomous.

Placing your component’s related files by their types, under different directories, is terribly cumbersome and not as clear to deal with.

5. Use Aliases

Reference other components using aliases. Having a path like the above makes it hard to move the component files around as you need to keep the reference valid. Using backward relative paths couples the component to the specific file structure of the project and forces the consuming project to use a similar structure. Webpack, Rollup, and Typescript provide methods for using fixed references instead of relative ones. Typescript uses the paths mapping to create a mapping to reference components. Rollup uses the @rollup/plugin-alias for creating similar aliases, and Webpack is using the setting of resolve.alias for the same purpose.

import { } from '@utils'

APIs

The general rule of thumb is to keep your components’ API surface to the absolute necessary minimum. Again, you should always aim for simplicity of use — reusable components should not have a learning curve.

6. Use Enums (Instead of Multiple Booleans)

Use Enums instead of multiple booleans. Interdependencies between parameters make your components harder to use.

For example, do this:

enum Location {

TopLeft,

TopRight,

BottomLeft,

BottomRight

} interface IProps {

location: Location

}

instead of this:

type Location = {

isLeft: boolean,

isTop: boolean

} interface IProps {

location: Location

}

7. Set Defaults

Setting defaults is yet another way to make your reusable components simple and easy to use. Customizing your reusable components should be your component-consumers prerogative, not a mandate.

(8) Restrict styling with themes

To provide better predictability to your component’s behavior (including its visual appearance) limit the degree of freedom your component consumers have in overriding your component styling properties.

Learn More