How to make strongly-typed React components

Published on Mar 10, 2021 · 2 min read

Hi everyone, in this article I am going to demonstrate some techniques that I use in my day to day work with TypeScript and React.
I love TypeScript. I consider it to be the best practice for setting up a new React project. TypeScript helps developers to improve their productivity and prevent them from producing bugs. Getting started with TypeScript-based React projects is really easy. Both create-react-app and Next.js support TypeScript out of the box.

Use union types

Using union types is really handy when we have a component which has a value-constrained prop. Let's say you want to create a Button component which accepts a variant prop. This variant prop can only accept any of these values: "default", "contained", and "outlined". The initial idea may be to type it as a string because those 3 values are all string. However, using string will introduce more complexity and can mislead other developers. To prevent those things, we might want to use union types so we can be sure that the component will not accept other values than "default", "contained", and "outlined". As a bonus, we can get auto-completion support from our IDE (e.g. VSCode).
import React from 'react';

interface Props {
  variant: 'default' | 'contained' | 'outlined';
}

const Button: React.FC<Props> = ({ children, variant }) => {
  return <button>{children}</button>;
};

export default Button;

Use generics

Let's say we want to build a generic list component which accepts an array of items and a render function.
interface Props {
  items: string[];
  renderItem: (item: string) => React.ReactNode;
}
Well, that works but only for list of strings. What if we want to display a list of numbers? Let's see how we can make this code accepts string and number.
interface Props {
  items: any[];
  renderItem: (item: any) => React.ReactNode;
}
This code works, but is is far from perfect. There is no guarantee that renderItem function is able to render provided items as it has any type. Let's see how we can make this code better using generic.
interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}
Now we can be sure that renderItem function accepts an item from items array.

Articles you might like