A complete example
Chapter 3: TypeScript for real-world applications
Chapters - Table of contents →
Let's combine all the knowledge on advanced and re-usable types by looking at a custom <Button> component built on top of material-ui's <Button> component.
Our UIKit exposes a <Button> component that provides the following props:
  • disabled?: boolean
  • href?: string
  • size?: 'small' | 'medium' | 'large'
  • loading?:boolean and loadingPlaceholder?: string
  • align?: 'left' | 'center' | 'right'
  • leftIcon and rightIcon (more on this later)
Here are some usage examples:
1import React from "react";
2import "./styles.css";
3
4import { Button } from "./Button";
5
6export default function App() {
7  return (
8    <div className="App">
9      <h1>Custom Button</h1>
10      <p>
11        <h2>Standard button</h2>
12        <Button>{"Click me"}</Button>
13      </p>
14      <p>
15        <h2>Loading button</h2>
16        <Button loading loadingPlaceholder="I'm loading!">
17          {"Click me"}
18        </Button>
19      </p>
20      <p>
21        <h2>Async button</h2>
22        <Button
23          onClick={() => new Promise((resolve) => setTimeout(resolve, 1000))}
24        >
25          {"Click me"}
26        </Button>
27      </p>
28    </div>
29  );
30}
31
The full code of <Button> is accessible here:
https://codesandbox.io/s/typescript-chapter-3-augment-material-ui-upl45?file=/src/Button.tsx
Take a few minutes to analyze the code before continuing to the section below.

<Button> anatomy

Let's take a look at some specific parts of <Button>'s typings.

ButtonProps

Partially extending material-ui's ButtonProps

Since our <Button> component is a wrapper around material-ui's <Button> component, ButtonProps is naturally extending a sub-set interface of material-ui's ButtonProps using Pick<T, K>.
This allows us to keep our custom <Button> component in sync with material-ui's <Button> component.

The case of rightIcon and leftIcon

Both props share the same base ButtonIcon type, however, only the rightIcon can receive a callback.
For this reason, rightIcon is composing ButtonIcon and ButtonIconActionable using the & operator.
Such type could have been built using an interface with extends, however, most of the time, we would prefer a shorter version leveraging inlines & or |.

Promisify<T> custom type helper

To avoid code duplication, our <Button> is shipped with a Promisify<T> type as follows:
1export type Promisify<T extends (...args: any) => any> = () => Promise<
2  ReturnType<T>
3>;
4
Such type allows us to write
1onClick?: ButtonCallback | Promisify<ButtonCallback>;
2
instead of:
1onClick?: () => void | () => Promise<void>;
2
Making it easier to maintain when ButtonCallback will change.

Conclusion

Our <Button> definition is a good example of leveraging TypeScript advanced types in order to build re-usable and maintainable types by:
  • extending external types instead of rewriting them: ButtonProps
  • extracting reused types: ButtonAlign, ButtonIcon, ButtonCallback
  • leveraging generics to extract common transformations: Promisify<T>
We use cookies to collect statistics through Google Analytics.
Do not track
 
Allow cookies