Caleb

Type-checking with TypeScript in React

I've never been a fan of TypeScript. But, here I am, writing about it — how Ironic!

I finally decided to give it a shot, since it was the default language that we used at the organization I worked briefly. I was passively learning it, BTW. So, this year, I was like: "why don't you give it a shot, fully!"

I'll be sharing all the findings I make as I progress, in the hopes that it helps you, my reader, and me, whenever I feel lost.

Coming from JavaScript's perspective

In React, it is ubiquitous for developers to resort to using the prop-types module in React for prop validation, and this can be done by declaring the shape of the type of prop(s) you want, which one should be required, which shouldn't, which one should be a default prop and many more.

Say we have a component that receives an array of objects — a list of articles — with key-value pairs like title, publishedDate, excerpt, and cover_image. Type-checking or prop validation in a React component can be attempted by taking the approach in the snippet below:

import React from 'react'
import propTypes from 'prop-types'

export default function ExampleComponent({ data }) {
  return (
    <React.Fragment>
      {data.map(
        ({ data: { title, excerpt, cover_image, publishedDate } }, index) => {
          return (
            <div className="container" key={index}>
              <img src={cover_image} alt="article cover" />
              <p className="title">{title}</p>
              <p className="date">{date}</p>
              <p className="description">{excerpt}</p>
            </div>
          )
        }
      )}
    </React.Fragment>
  )
}

ExampleComponent.propTypes = {
  title: propTypes.String.isRequired,
  excerpt: propTypes.String,
  cover_image: propTypes.String.isRequired,
  publishedDate: propTypes.String.isRequired,
}

And that's it. You may be wondering what exactly is going on with the map syntax in the component above. The snippet above uses the object destructuring pattern of JavaScript, but in this case, I'm using it with the map array method. You can read more about how you can destructure object properties in React with array.map()

prop validation with TypeScript

Achieving this same thing in TypeScript is a little bit similar, just that, this time around, the syntax may look a bit different. The snippet below illustrates how this is done.

The first thing you may have noticed is the interface keyword. I'll get to explaining what exactly its role is.

import React from 'react'
import propTypes from 'prop-types'

interface articleProps {
  data: Array<{
    title: String
    excerpt: String
    cover_image: String
    publishedDate: String
  }>
}

const ExampleComponent: React.FC<articleProps> = ({ data }) => {
  return (
    <React.Fragment>
      {data.map(
        (
          { title, excerpt, cover_image, publishedDate },
          index: React.key | null | undefined
        ) => {
          return (
            <div className="container" key={index}>
              <img src={cover_image} alt="article cover" />
              <p className="title">{title}</p>
              <p className="date">{date}</p>
              <p className="description">{excerpt}</p>
            </div>
          )
        }
      )}
    </React.Fragment>
  )
}

Instead of going through the process of using the propTypes declaration, the interface keyword enables you to define the shape that you want the props of a particular component to take. Alternatively, you can also declare the props you want with the types keyword too. Take a look at it;

type articleProps = {
  name: String
  age: String
}

In the component's snippet above, where I've used the interface keyword, you'll notice the angle brackets < > beside the Array data type assigned to the data prop, and the one besides React.FC. This symbol/syntax represents the type parameter of a specific component. It allows us to specify the type of props that a component receives.

And in my case here, I've already defined the props that the component will receive, with the interface keyword.

When you look closely at the component's snippet above, you'll also notice a slight change in the way the index parameter is already type-checked with TypeScript in the map() block, like so:

index: React.key | null | undefined

At first, I was just passing the index parameter like that, and JavaScript allowed my flaw. Normally, it shouldn't. But, with TypeScript, I got a warning, stating that the parameter needs a type definition.

Generally, if we're to go with the best practices, it is not even advisable to use the index parameter when you carry out a map() operation, why?, because the content of the supposed array can be modified (deleted, re-ordered), at any time, and using this index parameter may lead to some performance issues.

I recall Kent C. Dodds talking about how using the index parameter led to a production error related to representing the appropriate location of an item or so, when he worked at PayPal. It is always advisable to add an id key in your objects when you have an array that would be iterated over.

Final thoughts

Now, I'm beginning to see why people say "TypeScript does this, TypeScript does that". But my ego won't allow me to acknowledge its usefulness. Sorry, TypeScript Fan-babies!

I'll keep sharing my learning process around TypeScript here, so please, anticipate more articles around TypeScript in the coming months.