Technical Blog

How Readable Are Your React Component's TypeScript Props Typing?

August 15, 2021 - 11 minutes

Readable React

TypeScript is one of React’s most iconic partners. Some would even argue that TypeScript and React are a match made in heaven. One of the most common usages of TypeScript in a React codebase is typing a component’s props. It’s most likely that every React project with TypeScript will have props typing implemented for at least one component in some way. In a properly typed project, it’s not out of the ordinary that every component has props typings defined.

But despite it being so common and such a big part of a TypeScript React project, have you ever thought about how readable your component’s props typing is?

Nowadays, it’s very difficult to imagine any medium-sized or larger React project existing without having TypeScript. The differences between a React project with TypeScript and without are significant. If you have ever worked with both types of React projects, the impact of TypeScript will become very apparent. Even for small projects, its value can already be felt. Not only does it remove an enormous amount of ambiguity from JavaScript, but it also tremendously impacts the maintainability, scalability, safety, and organisation of the code project.

But just like how it’s important to keep our React code readable, the same applies to TypeScript code. In a decently sized typed React project, the fraction of TypeScript code is significant. It’s code that also needs to be maintained and thus important to ensure a degree of quality. To do so, it’s important to start with the basics and lay a good foundation for the rest of the typings.

This article will discuss and analyse three different ways of implementing the TypeScript implementation of a React component’s props typing. Not only will we look at the advantages and drawbacks of every approach, but we’ll also cover the readability and different use cases. This information will provide you with a solid foundation on how to implement a component’s props typing in a readable manner. After this article, you will be able to apply these approaches, identify when your code declines in readability, and keep more complex constructions readable by building on top of this knowledge.


To properly illustrate the different approaches, we will use one example problem in this article. We’ll implement the missing props typing using the different approaches in each respective section using this example. Based on the implementations, we’ll look into the advantages, drawbacks, use cases, and the degree of readability of each of them.

type Article = {
  id: string;
  coverImageUrl?: string;
  title: string;
  subtitle: string;
};

type ArticleCardsProps = {
  articles: Article[];
};

const ArticleCards = ({ articles }: ArticleCardsProps) => {
  return (
    <section className="articles-cards-container">
      {articles.map((article, index) => (
        <ArticleCard
          key={article.id}
          coverImageUrl={article.coverImageUrl}
          title={article.title}
          subtitle={article.subtitle}
          size={index === 0 ? "large" : "medium"}
        />
      ))}
    </section>
  );
};

type ArticleCardProps = ???

const ArticleCard = ({
  coverImageUrl,
  title,
  subtitle,
  size
}: ArticleCardProps) => {
  return (
    <div className={`article-card-container-${size}`}>
      <ArticleCardCover image={coverImageUrl} />
      <h2 className="article-card-title">{title}</h2>
      <div className="article-card-subtitle">{subtitle}</div>
    </div>
  );
};

In this example problem, we have a component that receives an array of articles, renders them into individual card components, and places them into a separate section on the screen. For the sake of this example, every article only has an identifier, an image URL for the cover, a title, and a subtitle. This is reflected in the Article type.

The second component, ArticleCard, takes those properties and renders them accordingly. On top of the article properties, this component also receives a size prop. This is an enumeration value that determined the size of the rendered article cards and is unrelated to the original article object.

The goal now is to implement the props typing for the ArticleCard component using all the information that has been provided. Let’s look into the different approaches at we can do so.


Write Out Every Prop Type

The most common way to implement a component’s props typing is by writing out every prop type explicitly. This means that the name and typing for every prop are individually defined inside the component props typing. Generally, it would look as follows:

type ArticleCardProps = {
  coverImageUrl?: string;
  title: string;
  subtitle: string;
  size: "small" | "medium" | "large";
}; 

The biggest advantage of doing it this way is that all the typing is always explicit. As mentioned, the name and type of every prop are explicitly written down. So it’s always clear which props are in the component’s props typing and which are not. All kinds of ambiguity are avoided using this approach, which on its own tremendously benefits the readability of this code.

Another advantage is that the reader has everything they need to understand the component’s props typing at a single location. Because of its explicitness, the reader never needs to reach out to any additional resources, typings, or files to understand the prop types. This prevents a lot of file and line switching, which allows the reader to avoid unnecessary distraction or losses of focus. This is extremely beneficial for the readability of your React code, especially when readers are reviewing your code in the browser where IDE support is almost non-existent yet.

Lastly, there is the topic of future adjustments. This could be a maintenance, refactoring, bug-fixing, or any other programming related activity that requires the React component’s props typing to be touched. Because this approach requires no external dependencies and everything in one place, being able to understand the existing code and making changes becomes very straightforward. No matter whether types need to be changed, removed, or added, they can just be typed as is without having to reference external resources.

The main drawback to this approach is its redundancy. All of the prop types are always repeated in every component’s props typing. Every prop is redefined using the most basic constructions, like primitive types. This is the equivalent of not reusing any variables in your JavaScript code and always instantiating them from scratch again. This introduces a lot of repeated code and redundancy, which has a snowballing negative effect on readability.

Another drawback is the lack of context and information about the relationships between typings. This applies to all the types in a React codebase, not only the component’s props typing. But it does directly affect them. This is a direct consequence of the redundancy in the TypeScript code that was just discussed. This lack of context and information about relationships between typings can be detrimental to future work. Not only does it affect the readability, but it also actively hinders future maintenance, refactoring, and bug-fixing.

Summary

  • ✅ Typing is always explicit.
  • ✅ Reader never needs additional resources, typings, or files to understand the prop types.
  • ✅ Future adjustments are straightforward.
  • ⛔ All of the typing is repeated.
  • ⛔ No context or information about relationships between typings.
  • ⛔ Typings needs to be adjusted by all users if the original typing changes.

Reference The Original Object Types

Another approach to implementing your React components’ props typing is by referencing the original object types. This is very similar to writing out prop type on its own, as every prop name is still individually defined. But instead of doing the same for the types, they are reused as much as possible from the original object types if they exist. In general, this would look as follows:

type ArticleCardProps = {
	coverImageUrl?: Article["coverImageUrl"];
  title: Article["title"];
  subtitle: Article["subtitle"];
  size: "small" | "medium" | "large";
};

The main advantage to implementing the component’s props typing using this approach is that we’re reusing the typings of the original object types. This explicitly establishes a connection between the newly created typings and the original object. Having this information about the relationships between typings isn’t only beneficial for future development related activities, but also for readability as it makes it easier to understand the context of the React code.

Another advantage is that the component we’re trying to type can always use whatever prop names they prefer. Whether it’s to avoid naming conflicts or for context purposes, sometimes it’s necessary to rename the original prop name. This approach allows you to freely do so without having to adjust the typings.

Lastly, when the types have to be adjusted during future development related activities it’s only necessary to do so in one place. Referencing the original object types like this in the component’s props typing is similar to applying the concept of Don’t Repeat Yourself (DRY) on the usual code. This is convenient for future maintenance, bug-fixing, or refactoring on typings as it’s only necessary to apply them in one spot. Because of the references, the props typing of all the components will be automatically up to date.

One drawback to implementing the component’s props typing using this method of referencing the original object types is that the resulting types are made implicit. Instead of stating the resulting types in the component’s prop type definition, the reader is only presented with a reference to another object type.

This means that the reader either has to navigate away from the current code they’re trying to understand and inform the referenced types. If they want to avoid this, then it’s required to have prior knowledge of the referenced typings. So either they have to switch between files and type definitions which obstructs their reading flow, or they are required to already know about the referenced types which usually can’t be expected. Both cases are not optimal and negatively affect the readability of the code.

Lastly, there is the topic of the amount of code. One of the reasons to apply the code concept of DRY is to reduce redundant code, which as mentioned helps with future development activities. Another one is to reduce the amount of code. The fewer code your readers have to go through, the lower the chance that they will get confused. The issue is that this approach of referencing the original object types doesn’t necessarily reduce the amount of code. Most of the time, it will result in an equal amount of code and thus not benefit the code readability in this aspect.

Summary

  • ✅ Reuses the typing of the original object.
  • ✅ Component can use whatever prop names they prefer but the typing remains.
  • ✅ Changing types is only necessary in one place.
  • ⛔ Makes the typing implicit.
  • ⛔ Doesn’t necessarily reduce the amount of code.

Reuse The Original Object As Prop Types

The final approach to implementing a React component’s props typing that we’ll discuss is to reuse the original object as prop types. Basically, this means that we’ll extend the component’s props typing with the original object types. This can be accomplished in several different ways involving TypeScript’s built-in utility types. But no matter how the approach is applied, the concept stays the same. Generally, it would look something like this:

type ArticleCardProps = Omit<Article, "id"> & {
  size: "small" | "medium" | "large";
};

// OR

type ArticleCardProps = Pick<Article, "coverImageUrl" | "title" | "subtitle"> & {
  size: "small" | "medium" | "large";
};

The main advantage to implementing a React component’s props typing by reusing the original object as prop types is that it reuses as much code as possible. It doesn’t reference or duplicate typings but basically extends existing typings. In this case, only a part of the original object is relevant, so it’s necessary to either specify the relevant properties or filter out the irrelevant ones. Because as much code as possible is reused, it will result in as little code as possible. Not only is this approach beneficial for specifying contextual relationships between types, but it also increases general readability due to less amount code.

Another advantage to this approach is that props are represented with the same name between components. Because we’re directly reusing the original object types, the prop names will never change and be consistent across different components and types. For readability purposes, this makes it easy to make the connection between different types, components, or code that is relevant to that prop.

Lastly, this approach does a great job of highlighting what’s different and what’s not. In this approach, we’re reusing the original object as prop types, but we don’t always need all of them. To address that, either the irrelevant ones have to be filtered out or the relevant ones have to be filtered in. No matter which method is used, it creates an explicit definition of what’s similar, what’s different, or what’s extended. This type of explicitness is important for the readability of the code, especially when readers are reviewing your code in the browser where IDE support is almost non-existent yet.

The main drawback to implementing a React component’s props typing by reusing the original object as prop types is that it heavily relies on familiarity with the referenced typings. Because we’re directly picking or filtering out from a different typing, it’s very difficult to understand the code without any prior knowledge of that typing. Requiring prior or implicit knowledge from your readers is a very strong requirement, one that is very rarely satisfied.

This means that readers will have to navigate between files and code when reading through your code and trying to understand it. Especially when doing so in the browser where IDE support is almost non-existent at the moment, having to make these frequent context switches hinders the reader a lot. This can accumulate to a significant negative impact on the readability of your code.

Another drawback to this approach is the fact that it relies on the reader knowing the ins and outs of most TypeScript utility functions. In the above example, two different TypeScript utility functions were used to illustrate implementing this approach. In reality, there are a lot more utility functions available that can accomplish the same result.

Using these to implement a React component’s props typing comes with an assumption that the reader is familiar with that particular utility function. Whether this is a realistic assumption to make depends on your team or reviewers. Even if it’s not realistic, the overhead for looking it up in documentation is not big, but it’s still a context switch that the reader has to perform. This will add up to the perceived difficulty of reading and understanding your code by the readers.

The last drawback is more a factor that needs to be taken into consideration and is related to the amount of typing that can be reused. If there is a lot to reuse, then there isn’t really an issue. But having fewer types that can be reused will enhance the negative impact of the previous drawbacks. Implementations will be more specific, require different utility functions, or become more verbose. This in turn will lead to an increased effect of the previous drawbacks. Therefore, the impact on the readability of this approach is closely related to the fraction of types that can be reused, which isn’t a great dependency to have.

Summary

  • ✅ Reuse as much code as possible, resulting in as little code as possible.
  • ✅ Same props are represented with the same name between components.
  • ✅ Does a great job of highlighting the similarities, differences, or extensions.
  • ⛔ Heavily relies on implicit knowledge about the referenced typings.
  • ⛔ Depends on the reader knowing the ins and outs of most TypeScript utility functions.
  • ⛔ The smaller fraction of types that can be reused, the higher the impact on readability.

Final Thoughts

In this article, we discussed three approaches to implementing a React component’s TypeScript props typing. Either writing out every prop type explicitly, referencing the original object types inside the props typing, or even reusing the original object directly as prop types for the component. All of them have their advantages, drawbacks, and use cases in terms of readability. The information in this article will provide you with a solid foundation on different ways to implement TypeScript props typing for your React components in a readable manner. You will be able to apply these approaches, identify when your code declines in readability, and keep more complex constructions readable by building on top of this knowledge.


After graduation, my career is entirely centered around learning and improving as a developer. I’ve began working full time as a React developer and I’ll be blogging about everything that I encounter and learn during this journey. This will range from improving communicational skills in a technical environment, becoming a better developer, improving technical skills in React and JavaScript, and discussing career related topics. In all of my posts, the focus will be on my personal experiences, learnings, difficulties, solutions (if present), and also flaws.

If you’re either interested in these topics, more personalised technical stories, or the perspective of a learning developer, you can follow me either on Twitter or at Dev to stay up to date with my blogposts. I’m always learning, so stay tuned for more stories! 🎉