A quick tip for Props & Types in Reusable Components
If you work on a codebase made up of a bunch of reusable components you have probably encountered the “props blowout” situation — a component that has so many props that it’s confusing to work with and no one really knows what each one does and how they affect each other.
A good example of this might be a traditional “Card” component (something like figure 1). Card components can easily become unwieldy as you add props to toggle images, kickers, topics, bylines, source, timestamps, layout, or any number of variations and options.
Our approach to components is to break the card component up into multiple smaller pieces and build it up using these smaller chunks. For example, we have “CardMedia” which loads in an image or a video, “CardHeadline” which represents the headline, and so on until we have all the pieces of the puzzle. But even within these components you can still end up with a lot of variation and a lot of props.
Using some of the props we have on one of our components as an example we will look at how we can improve the typings to create a component that is much more resilient and easier to work with.
Below we have a number of props that determine different ways the media in our component can be displayed, these props are not exactly what we had originally but it’s a loose representation of a situation you might find yourself in, and is does reflect some real life situations I’ve seen in the wild.
interface Props {
hideMedia?: boolean
mediaIsEdgeToEdge?: boolean
mediaFullHeight?: boolean
videoInline?: boolean
}
The purpose of these props are to change the way the image or video is rendered within the card or if the media is rendered at all. The problem with defining them separately is that you end up with a number of flags which toggle component features, many of which are mutually exclusive. For example, you can’t have an image that fills the margins if it’s also hidden. As a result this kind of approach can cause a lot of bugs, rendering issues and confusion, especially if you have a lot of different options.
So a better approach to this would be to create a prop like mediaMode
(or whatever…naming things is hard). Then rather than using a boolean type, change it to be a union, it might look like this:
type MediaMode = 'hidden'| 'edgeToEdge' | 'fullHeight'
This allows you to specify the different layout options but removes the overlap. So instead of having multiple props that could affect each other or cause collisions you can replace them with one. (The combinations of different flags and the exponential increase of possible states and combinations is also known as combinatorial explosion).
interface Props {
mediaMode: 'hidden'| 'edgeToEdge' | 'fullHeight'
}
What this achieves is cleaner more concise types, it restricts the scope and prevents multiple “layout” options being applied, potentially causing rendering bugs. It also makes working with and refactoring the component significantly easier and provides the flexibility to add new options.
So next time you are adding props to a component, rather than just adding new ones and creating a large unwieldy confusing mess, consider how each prop relates to each other and whether it would be better to group them into a union type, with a more meaningful name, rather than defining them separately and having a bunch of potentially confusing props.
Mandy.