Converting a React function component to TypeScript

The project I'm currently working on is running on TypeScript. Since it's a greenfield project and it runs on React 16.8+, we strive to create the front-end by using only function components.

There's multiple arguments for using TypeScript, but the main reason is that we (obviously) want to minimize the risk of us introducing bugs in our codebase. Other reasons for why we're running on TypeScript are listed below:

Why I use TypeScript

  • ✅ Strict type checking of React component props
  • ✅ Easier debugging of dependencies and third party packages
  • ✅ Strict checking of null values
  • ✅ Know what parameter(s) a function expects and what it will return
  • ✅ Force consistent casing of filenames
  • ✅ Remove comments when compiling to JavaScript
  • 💡 ... and there's a lot more in the compiler options

All in all, using TypeScript with your React web app will result in a more confident and (eventually) developer experience, especially when you're working on a complex app.

A React component in JavaScript, without TypeScript (.jsx)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* ./src/components/navigation/Navigation.jsx */

// Import React
import React from "react"

// Define mock theme data
const theme = "dark"

// Define mock menu item data
const items = [
  {
    url: "/",
    text: "Home",
  },
  {
    url: "/blog",
    text: "Blog",
  },
  {
    url: "/contact",
    text: "Contact",
  },
  {
    url: "https://external.url",
    text: "External link",
    target: "_blank",
  },
]

// Create a React component
const Navigation = ({ theme, items }) => (
  <nav className={theme === "dark" ? "dark" : "light"}>
    {items.map((item) => (
      <a target={item.target && item.target} href={item.url}>
        {item.text}
      </a>
    ))}
  </nav>
)

// Export the Navigation variable so we can import it in other files
export default Navigation

In a production app the variables theme and items will be passed from another component to this one, but for now we're mocking the data so we can focus on implementing TypeScript.

There's quite an improvement to make here. With TypeScript we can make our variables and functions strict. Let's convert this regular React component to a component which uses TypeScript:

The same React component in TypeScript (.tsx)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/* ./src/components/navigation/Navigation.tsx */

// Import React
import React, { FunctionComponent } from "react"

// Import TypeScript types and enums
import {
  TNavigationProps,
  TNavigationItems,
  TNavigationItem,
  ThemeEnum,
} from "./navigationTypes.ts"

// Define mock theme data
let theme = ThemeEnum.Dark

// Define mock menu item data
const items: TNavigationItems = [
  {
    url: "/",
    text: "Home",
  },
  {
    url: "/blog",
    text: "Blog",
  },
  {
    url: "/contact",
    text: "Contact",
  },
  {
    url: "https://external.url",
    text: "External link",
    target: "_blank",
  },
]

// Create a React component
const Navigation: FunctionComponent<TNavigationProps> = ({ theme, items }) => (
  <nav className={theme === ThemeEnum.Dark ? "dark" : "light"}>
    {items.map((item: TNavigationItem) => (
      <a target={item.target && item.target} href={item.url}>
        {item.text}
      </a>
    ))}
  </nav>
)

// Export the Navigation variable so we can import it in other files
export default Navigation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* ./src/components/navigation/navigationTypes.ts */

// Define enum for theme names
export enum ThemeEnum {
  Dark = "dark",
  Light = "light"
}

// Define props for navigation items
export type TNavigationItemProps = {
  url: string
  text: string
  target?: string
}

// Define props we want to pass to the Navigation component
export type TNavigationProps = {
  theme: ThemeEnum
  items: TNavigationItemProps[]
}

I have created an extra file (navigationTypes.ts) were I store this components TypeScript variables (like types and enums).

I'm strict on saving these in a seperate file and not typing my functions or variables inline, as I just want to know where to look when I'm developing and debugging.

Let's go over the differences and elaborate on how we typed a React component:

  • Line 4
    I'm importing and using the FunctionComponent interface from the React library, we have to use this later on to tell TypeScript that our component is a Function Component.
  • Line 7 to 12
    We're importing our TypeScript types and enums in our component. For more information on why I use types and not interfaces, read this article.
  • Line 15
    Define the default theme and with the colon, tell TypeScript that it should match one of the enums. This is a let because it might change over time (when a user toggles the current theme)
  • Line 18
    Create mock data to test our component. The :TNavigationItems part tells TypeScript that the structure of this object should match the type TNavigationItems. Notice that not all items have target: "_blank". This is optional and declared with a questionmark in the typings file.
  • Line 39
    We're using the FunctionComponent<TNavigationProps> generic interface to make sure TypeScript notifies us when we either do not return a React component or a null (that's the FunctionComponent part) and that we expect certain properties (that's our own TNavigationItems part).
  • Line 40
    We're comparing the current theme variable (defined on line 15) with the TypeScript Enum. If the value equals the "dark" value in the enum, we know the theme is dark. If it doesn't, it's the "light" theme.
  • Line 41
    I type the arrow function used in the JavaScript map function, so TypeScript knows what to expect within this function.

That's it.

A React function component typed with TypeScript! Remember, it might take a little more effort to setup and get used to typing your variables and functions, but it will give you a better developer experience in the long run.

A working example

Have a look at a working example of above code on CodeSandbox: https://codesandbox.io/s/davevanhoorncomconverting-a-react-function-component-to-typescript-zx620