loading...
Cover image for React + TypeScript ❤️: The good parts ⚡

React + TypeScript ❤️: The good parts ⚡

diemax profile image Diego Maximiliano Updated on ・6 min read

I've started using TypeScript with React, and after a while, learning, and implementing them together. This is how I feel:


I couldn't believe how much my DX has improved. I'll leave you the main features I enjoy the most so hopefully you will feel excited to give it a shot 😃 !

Disclaimer ⚠️

This is not a beginner-friendly introduction to TypeScript, you need to know at least how to use Interfaces, however, at the end of the article I will drop a list of great resources I found very useful along with the fastest way to get React + TypeScript up and running and start hacking:

$ npx create-react-app my-app --template typescript
# or
$ yarn create react-app my-app --template typescript

TLDR;

Pros:

  • Code completion and type validation with type definitions.
  • Great community and learning resources.

Cons:

  • TypeScript syntax and concepts like Generics can be hard to understand.

Table Of Contents

IntelliSense

I use VSCode and one of its best features is IntelliSense (A.K.A Code Completion), TypeScript takes advantage of it.
Let's say we have a simple List component, and we declare the Props Interface, in this case one prop labels, an Array of strings:

// List.tsx
import React from 'react';

interface Props {
  labels: string[]
}

// We tell TypeScript the parameter type 
// + Object destructuring 👇
function List({ labels }: Props): React.Element {
  return (
    <ul>
      { labels.map((label, index) => {
        <li key={index}>{label}</li>
      )}
    </ul>
  );
}

const strings = ['React', 'TypeScript', '❤️'];
<List labels={strings}/>

This is what we get:

IntelliSense

If we type labels. inside List we get the code completion with methods and attributes from Array.prototype, in this case Array.prototype.forEach() and then inside the callback function we get all methods and attributes from String.prototype, in this example Array.prototype.toUpperCase().
VSCode is written in TypeScript, it has a built-in type definitions for Native Objects, and the best part? We can declare our own types, and most libraries we use come along with their own type definitions files already 😃 !
Which bring us to the next great feature:

Type Definitions

React and TypeScript community is awesome, they've created a huge centralized repository of High Quality Type definitions files:

GitHub logo DefinitelyTyped / DefinitelyTyped

The repository for high quality TypeScript type definitions.

Most libraries keep their type declarations files on this repo, and sometimes we have to install them independently, on the official website we can search and find instructions to install them, in the case of React, for example:

$ yarn add @types/react --save-dev

How can we use them?

import React from 'react';

interface Props {
  labels: string[]
}

function List({ labels }: Props): React.Element {
  const styles: React: React.CSSProperties = {
    backgroundColor: 'blue'
  }
  // ...
}

Let's say we want to write some inline CSS, in this case we can use the React built-in type definition React.CSSProperties, it'll show us an error if we write a typo like backgroundKolor and we'll get code completion for the properties 😁.
And we can also see the source code so we get used to read and write them.
We already learned how take advantage of type definitions to get Code Completion, but now we'll learn another powerful feature that comes with them:

Type Validation

What if we either don't pass the right prop type or we don't pass it at all?

interface Props {
  labels: string[]
}

function List({ labels }: Props) {
 // ...
}

const strings = ['React', 'TypeScript', '❤️'];
const numbers: number[] = [1, 2, 3];

// 1) We pass an array of numbers
<List labels={numbers} />
// 2) We don't pass it
<List />

In both cases we get an Error 🚫:
Alt Text

These errors are clear, they tell us what's the type error, where that declaration was made, if the prop is actually required, and this applies to functions also (although React Components are just functions 😉).
The TypeScript compiler won't be happy until we pass the right parameters, this is useful to see possible bugs ahead even before compiling the code and checking the browser.

What about prop-types?

Yes, we can achieve the same validation using prop-types:

import React from 'react';
import PropTypes from 'prop-types';

function List({ labels }) {
  // ...
}

List.propTypes = {
  labels: PropTypes.arrayOf(PropTypes.string).isRequired
}

However, since prop-types checks our code during runtime, we have to compile it first, see the actual error on the console, and also, this validation just happens on development mode 😟, whereas TypeScript analyses our code statically.

Hooks

When it comes to React Hooks, useState for example, TypeScript can be super handy, specially with type validations.

function Counter() {
  // we can also use brackets <> syntax for types declarations:
  const [counter, setCounter] = useState<number>(0);
  const add = () => {
    // this is gonna give us an error 😱
    setCounter('string');
  }
  return(
    <div>
      <button onClick={add}>+</button>
      {counter}
    </div>
  );
}

In this way we make sure every time we update the state we preserve the value type, this can save us hours of debugging and headaches.

The Bad parts. 🤐 Trade-offs

We learned how TypeScript can benefit the whole team when it comes to write components, just let's imagine we write our (ideally) reusable components library with well defined types and our colleague import one of them, they are gonna see beforehand:

  • Props types and if they are required or not.
  • Code completion for prop names.

This can save us time browsing source code to make sure we pass the right data to every component.
But we also know that in Software Development there is no Silver Bullet. Every tool we choose comes with a trade-off, in the case of TypeScript, of course there are some:

Reading TypeScript can be hard:

interface Array<T> {
  concat(...items: Array<T[] | T>): T[];
  reduce<U>(
    callback: (state: U, element: T, index: number, array: T[]) => U,
    firstState?: U
  ): U;
  // ···
}

But don't worry, I got this snippet from this great article which explains every detail, that example was really confusing for me at first.

Some concepts can be complicated to grasp:

  • Interfaces.
  • Generics.
  • Interfaces vs Types.

These new concepts (especially if members of our team aren't used to them) can bring more questions and confusion.
Of course we can tackle them, it just depends on our team members experience, available time, and feeling eager to learn new things.
This article reflects the idea I've been thinking about for a while and it's likely to be my next post topic, everything in Software Development is a trade-off:

Resources

These are the best resources that helped me to understand and love TypeScript:

Conclusion

responsability
TypeScript comes with a lot of benefits and constrains, but our context (team, priorities, goals) can define if we can get more advantages than cons and make our lives easier!
IMHO when it comes to large scale applications, and specially if we are building a bullet-proof architecture or System Design, the effort pays off, we can write less bugs-prone code and deliver features faster, and safer.

Thanks for reading, if you found this article useful please follow me on Twitter and let me know what you think!

Happy coding!

Posted on by:

diemax profile

Diego Maximiliano

@diemax

JavaScript, React @ https://mega.nz

Discussion

pic
Editor guide
 

Nice post! I like using the the React.FC type for functional components. Your List component would look like this:

const List: React.FC<Props> = ({ labels }) => {
  // ...
}
 

React.FC adds a children prop which we don't want in this example.

 

Great article! I was especially interested since I tried out TypeScript without much background on it in a brand new project at work a few months ago. I ~70% loved it, there were just some really annoying parts I came across that I don't know were other from my naivety or just trade-offs with TypeScript:

  1. Quickly prototyping a component or a helper function is very annoying having to be very explicit with everything, since I'm just trying to get an idea or concept down.

  2. I don't know if I ever fully grasped Generics. My understanding of it is that if you pass a type to an instance of the component/function that uses the generic, that should be the type of passed. In numerous components and functions, I tried to do something like this (ex: a Carousel component that can take both an array of strings or numbers as an "items" prop, or a reduce function that takes an object with a nested array, and it should flatten that array). I just felt like I was constantly having to import/export interfaces and declare them as types on component props or function arguments, and no pun intended, prevented me from keeping them "generic".

  3. Mapping data received from server into the shape that was desired for my pages/components. I found myself having to first declare an interface with the shape of the data received from the server, and then would have to declare yet another interface with the returned value(s) from my mapping. This would turn into an even bigger mess if there were nested objects that had to be transformed. It just seemed so unnecessary to do since I was using this data once after receiving it from fetch, but simply setting any to it is just cheating TypeScript.

Like I said, some of this could just be me not being a full pro in TS, so I appreciate the Resources here to see maybe if I can further my understand. Was interested if you came across any similar issues to me! Thanks

 

Gracias por redactar esta info ~
(vi que tu primer post está en español jaja)

 

Excellent post Diego! very helpful

 

Great article!!

 

Just the post I was looking for I will be trying TypeScript with React later.