How To Eliminate Bugs In React - 4 Examples Using TypeScript

How To Eliminate Bugs In React - 4 Examples Using TypeScript

I like to use TypeScript for basically all of my new React projects.

TypeScript is fantastic to use with React?because it can help us catch a lot of bugs we wouldn't catch otherwise. It helps our team have good documentation and it makes React?easier to use.

But there were a lot of things I didn't understand in the beginning that I wish someone would have explained to me.

Below I'm going to walk you though the basics of using TypeScript to type React code and eliminate potential bugs in our code.

We're going to look at:

  • how to create a new TypeScript and React project (it's built in now)
  • how to type?props?for a?class?component
  • how to type?state?for a class component
  • how to type?props?for a?functional?component
  • how to type?state?for a functional component (with?hooks)

The key idea is that we want to define the "shape" of our?props?and?state?with TypeScript types -- and TypeScript will ensure that everything conforms to the right "shape" in our code.

Here's the?interface?we're going to be using for?props?and?state?throughout this email:

interface CounterProps {
  message: string;
}

interface CounterState {
  count: number;
};        

If you're not familiar with TypeScript interfaces yet, the above code says that any object of the?CounterProps?type must have a?message?key that is a?string. We'll look at more code below.

Creating a TypeScript React Project

So for getting started, thankfully,?create-react-app?has a?typescript?template that has everything setup for us.

You can create a new project like this:

npx create-react-app --template typescript counter        

It will create a bunch of files and the main addition is the?tsconfig.js?file which configures the TypeScript compiler.

You'll also notice that our files end in?tsx?instead of?jsx.

Also, if you look at the?package.json?file you'll see some extra typescript packages, too - but because they work out of the box, we'll ignore those for now.

The?main?thing we're going to be doing with TypeScript and React is defining components. So it makes sense to look at the typing of those components first.

As you may know, there are two ways you can define components:

  1. Class components or?
  2. Function components?

Let's look at both:

Typing Class Components with TypeScript

To keep it simple, let's take the classic case of a component that shows a counter with a message.

A bare-minimum TypeScript class component looks like this:

import React from "react";
class Counter extends React.Component {
  render() {
    return <div> Hello: 1 </div>
  }
}                      

So far, so good, but we're missing two key parts of React components:

  • props and
  • state

In React, we use?props?to pass down values into a component and?state?to manage state within a component.

So let's define an interface for the props to this component. We want to accept a?message?into this component as props like this:

<Counter message="Hello" />        

If you think about it, what we're doing is passing this object into the component:

{ 
  message: "Hello"
}        

We can define an?interface?in TypeScript that describes this object. We'll do it like this:

interface CounterProps {
  message: string;
}        

Adding Props Type

But how do we tell React that the?CounterProps?interface is the type for our component props?

We do this by using?TypeScript?generics?on the?React.Component?type.

You can think of generics as?parameters to a type. Generics let you make "configurable" types. It's easier if we look at the code:

import React from "react";

interface CounterProps {
  message: string;
}

class Counter 
 extends React.Component<CounterProps> {
  render() {
    return <div>{
      this.props.message} 1</div>;
  }
}        

Now let's say we try to call this component?without?passing a?message?prop like this:

function App() {
  return (
    <div className="App">
      <Counter />
    </div>
  );
}        

... we get an error:?Property message is missing

Okay, so we know we need to pass a?message?prop. Let's try passing an?invalid message?prop and see what happens:

function App() {
  return (
    <div className="App">
      <Counter message={123} />
    </div>
  );
}                      

Above, the number?123?is invalid because we defined our?message?to be a?string(not a?number).

Again, we get an error:

Type 'number' is not assignable to type 'string'

This is how we prevent potential bugs from going into production code.

Typing State

Okay so that deals with typing props, what about state? We deal with that much the same way, first we'll define an interface for the state:

Let's keep our counter in state as the variable?count:

interface CounterState {
  count: number;
};        

Now we pass the?CounterState?as the?second?parameter to the?React.Component?generic:

class Counter extends React.Component<CounterProps, CounterState> {
  state: CounterState = {
    count: 0,
  };

  render() {
    return (
      <div>
        {this.props.message} 
        {this.state.count}
      </div>
    );
  }
}        

Looking at the above code, you might be wondering,?how would I ever know that state was the second generic parameter?

It's in the documentation (for the types). Just like how you learn the order of function parameters in, say?setTimeout, you look at the docs to learn the order of parameters for generics.

Functional Components

Typing class components is interesting, but they're also falling out of fashion. So how would we type functional components?

Well, the first way is that?you don't have to add any types at all.

export const Counter = () => {
  return <div>Hello: 1</div>;
};        

Because we are returning JSX, TypeScript can automatically conclude that the return type of our function is?JSX.Element, which is good enough for React.

Of course, if you want to be verbose, you can use?React.FC:

export const Counter: React.FC = () => {
  return <div>Hello: 1</div>;
};        

But what about props?

Well, with a functional component,?props are just arguments?so we can just type the arguments:

export const Counter = (
  props: CounterProps) => {
  return <div>{props.message}: 1</div>;
};                      

Above we type the?props?as?CounterProps. However, a more idiomatic React style would be to?destructure?the props into variables like this:

export const Counter = ({
   message }: CounterProps) => {
  return <div>{message}: 1</div>;
};        

What about functional state?

When we have a functional component we use?hooks?to manage state. Specifically we're going to use the?useState?hook.

Here's what our component looks like with keeping a basic counter with?useState:

export const Counter = ({ message }: CounterProps) => {
  const [count, setCount] = useState(0);
  return (
    <div>
      {message}: {count}
    </div>
  );
};                      

So in this case, if I was keeping simple values I'd not need to add any extra types at all. If we keep one value within?useState, it's pretty easy.

However, if you've worked with React long enough you're probably suspicious of my solution here. What about in the more complicated case where our state is an?object, like in our class above?

Now, I'd like to point out that in such a simple case I probably wouldn't store an object in?useState?as a matter of style. But I want to show you a more complicated case as it represents something that might come up in your work.

Say we want our state to be the same?CounterState?object:

interface CounterState {
  count: number;
}        

To type this, we use the?generics on?useState:

export const Counter = ({ message }: CounterProps) => {
  const [state, setState] = useState<CounterState>({ count: 0 });
  return (
    <div>
      {message}: {state.count}
    </div>
  );
};                      

Above, notice the line with?useState?- there, we're telling TypeScript that the object in?useState?is of type?CounterState?- and so it needs to enforce that "shape" on any value it controls.

So there you have it, that's the absolute basics of using React with TypeScript, and eliminating potential sources of bugs before they make it to production.

But there's a lot more advanced techniques you can learn, things like:

  • How do we type more complex hooks like?useReducer?
  • How do we add types for a custom React library?
  • What if we're using GraphQL and we want typed queries?

Till next time,

要查看或添加评论,请登录

Morteza Mirjavadi的更多文章

  • Zod as a structural checker

    Zod as a structural checker

    To check response structure using Zod, a TypeScript-first schema declaration and validation library, you can create a…

    2 条评论
  • What is Transduction in Functional Programming?

    What is Transduction in Functional Programming?

    In functional programming, transduction refers to a technique for composing transformations on data (such as mapping…

社区洞察

其他会员也浏览了