DEV Community

Cover image for How to write correctly typed React components with TypeScript
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

How to write correctly typed React components with TypeScript

Written by Piero Borrelli✏️

If you are a software developer — especially if you write JavaScript — then you have probably heard of TypeScript. Hundreds of courses, forum discussions, and talks have been created regarding this technology, and interest is still growing.

TypeScript is a strict, typed superset of JavaScript developed by Microsoft. It basically starts from the usual JavaScript codebase we all know and compiles to JavaScript files, while adding some very cool features along the way.

JavaScript is a dynamically typed language, and love it or hate it, it can be a very dangerous behavior. In fact, it can cause subtle issues in our program when some entities are not used as intended.

With TypeScript, we can avoid these kinds of errors by introducing static types. This mechanism will save us a lot of time in debugging since any type error will prevent you from running your code. And also note that usage of types is completely optional; you will be able to use it discretely whenever you think it’s necessary in your code.

With TypeScript, you will also able to use the most recent ES6 and ES7 features with no need to worry about browser support. The compiler will automatically convert them to ES5, leaving you space to focus on more important parts of your project and saving time spent testing browser compatibility.

LogRocket Free Trial Banner

Integrating TypeScript with other technologies

As you may have intuited, TypeScript can be a true game-changer for your project, especially if you believe it will grow in size and you want to have the best options for managing it. At this point, you may be wondering how you can integrate TypeScript with another technology you’re using.

In this case, the language itself comes in handy by providing support for many frameworks. In this guide, we are going to check out how this amazing language can be integrated into the most popular frontend framework out there: React.

The React case

TypeScript is at its best position right now when it comes to using it for React applications. You will be able to use it to make your products more manageable, readable, and stable. The integration has become extremely easy, and in this case, my advice for you is to set up your favorite environment in order to try out the examples proposed in this article.

Once everything is set up, we can start exploring our new TypeScript + React integration.

Typed functional components

Functional components are one of the best-loved React features. They provide an extremely easy and functional way to render our data. These types of components can be defined in TypeScript like so:

import * as React from 'react'; // to make JSX compile

const sayHello: React.FunctionComponent<{
  name: string;
}> = (props) => {
  return <h1>Hello {props.name} !</h1>;
};

export default sayHello;
Enter fullscreen mode Exit fullscreen mode

Here we are using the generic type provided by the official typings — React.FunctionComponent, or its alias React.FC — while defining the expected structure of our props. In our case, we are expecting a simple prop of type string that will be used to render a passed-in name to the screen.

We can also define the props mentioned above in another way: by defining an interface using TypeScript, specifying the type for each one of them.

interface Props {
  name: string
};

const sayHello: React.FunctionComponent<Props> = (props) => {
  return <h1>{props.name}</h1>;
};
Enter fullscreen mode Exit fullscreen mode

Please also note that using React.FunctionComponent allows TypeScript to understand the context of a React component and augments our custom props with the default React-provided props like children.

Typed class components

The “old way” of defining components in React is by defining them as classes. In this case, we can not only manage props, but also the state (even if things have changed since the latest release of React 16).

These types of components need to be extended from the base React.Component class. TypeScript enhances this class with generics, passing props and state. So, similar to what we described above, class components can be described using TypeScript like so:

import * as React from 'react';

type Props {}
interface State {
  seconds: number;
};
export default class Timer extends React.Component<Props, State> {
  state: State = {
    seconds: 0
  };
  increment = () => {
    this.setState({
      seconds: (this.state.seconds + 1)
    });
  };
  decrement = () => {
    this.setState({
      seconds: (this.state.seconds - 1)
    });
  };
  render () {
    return (
      <div> <p>The current time is {this.state.seconds}</p> </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Constructor

When it comes to the constructor function, you have to pass your props (even if there are none), and TypeScript will require you to pass them to the super constructor function.

However, when performing your super call in TypeScript’s strict mode, you will get an error if you don’t provide any type specifications. That’s because a new class will be created with a new constructor, and TypeScript won’t know what params to expect.

Therefore, TypeScript will infer them to be of type any — and implicit any in strict mode is not allowed.

export class Sample extends Component<MyProps> {
  constructor(props) { // ️doesn't work in strict mode
    super(props)
  }
}
Enter fullscreen mode Exit fullscreen mode

So we need to be explicit on the type of our props:

export class Sample extends Component<MyProps> {
  constructor(props: MyProps) {
    super(props)
  }
}
Enter fullscreen mode Exit fullscreen mode

Default props

Default properties will allow you to specify the default values for your props. We can see an example here:

import * as React from 'react';

interface AlertMessageProps {
  message: string;
}

export default class AlertMessage extends React.Component<Props> {
  static defaultProps: AlertMessageProps = {
    message: "Hello there"
 };

  render () {
    return <h1>{this.props.message}</h1>;
  }
}
Enter fullscreen mode Exit fullscreen mode

Typing context

Typically, in a React applications, data is passed down to every component via props in a parent-to-children approach. However, it can sometimes become problematic for certain types of information (user preferences, general settings, etc.).

The Context API provides an approach to avoid the need to pass data down to every level of a tree. Let’s check out an example of this using both JavaScript and TypeScript:

Javascript

const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Using a Provider to pass the current theme to the tree below.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// Middle component doesn't need to pass our data to its children anymore
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}
// React here will find the closest theme Provider above and use its value("dark")
class ThemedButton extends React.Component {
  // contextType to read the current theme context
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
Enter fullscreen mode Exit fullscreen mode

TypeScript

Using this feature with TypeScript simply means typing each Context instance:

import React from 'react';

// TypeScript will infere the type of this properties automatically
export const AppContext = React.createContext({ 
  lang: 'en',
  theme: 'dark'
});
Enter fullscreen mode Exit fullscreen mode

We will also see useful error messages available in this case:

const App = () => {
  return <AppContext.Provider value={ {
    lang: 'de', 
  // Missing properties ERROR
  } }>
    <Header/>
  </AppContext.Provider>
}
Enter fullscreen mode Exit fullscreen mode

Typing custom Hooks

The ability for developers to build their custom Hooks is really one of the React’s killer features.

A custom Hook will allow us to combine the core of React Hooks into our own function and extract its logic. This Hook will be easily shared and imported like any other JavaScript function, and it will behave the same as the core React Hooks, following their usual rules.

To show you a typed custom Hook, I have taken the basic example from the React docs and added TypeScript features:

import React, { useState, useEffect } from 'react';

type Hook = (friendID: number) => boolean;

// define a status since handleStatusChange can't be inferred automatically
interface IStatus {
  id: number;
  isOnline: boolean;
}

// take a number as input parameter
const useFriendStatus: Hook = (friendID) => {
  // types here are automatically inferred
  const [isOnline, setIsOnline] = useState<boolean | null>(null);

function handleStatusChange(status: IStatus) {
  setIsOnline(status.isOnline);
}
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  return isOnline;
}
Enter fullscreen mode Exit fullscreen mode

Useful resources

Here I’ve compiled for you a list of helpful resources you can consult if you decided to start using TypeScript with React:

Conclusion

I strongly believe that TypeScript will be around for a while. Thousand of developers are learning how to use it and integrating it into their projects to enhance them. In our case, we learned how this language can be a great companion to write better, more manageable, easier-to-read React apps!

For more articles like this, please follow my Twitter.


Editor's note: Seeing something wrong with this post? You can find the correct version here.

Plug: LogRocket, a DVR for web apps

 
LogRocket Dashboard Free Trial Banner
 
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
Try it for free.


The post How to write correctly typed React components with TypeScript appeared first on LogRocket Blog.

Top comments (1)

Collapse
 
pretaporter profile image
Maksim

You can use React.FC instead of React.FunctionComponent. It is short synonym)