How to write React components in TypeScript

By Tom Hastjarjanto

Introduction

React is a small library, yet it offers its users a lot of flexibility in how to use it. React is a library that allows users to create component-based applications, so naturally creating components is what will be the foundation of your application code. However, React offers multiple ways to define components, and is not very clear in which cases which method is preferred. This raises a lot of questions from both beginners and experienced React developers. Is there a best way to write a React component? In a codebase consistency is a property that is highly valued for the maintainability of your application.

React component definitions

React.createClass

The original way to create React components was through calling the function React.createClass. An object containing the lifecycle methods was passed to define the component. It is unlikely you will encounter components using this method in the current codebase since this method has been deprecated since React 15.5.0.

// https://jsfiddle.net/vjeux/VkebS/
var Hello = React.createClass({
  render: function () {
    return React.DOM.div({}, "Hello " + this.props.name);
  },
});
Hello = React.createFactory(Hello);

React.render(Hello({ name: "World" }), document.body);

ES6 Class-based components

React introduced ES6 class-based components in React 0.13. Class-based components are the most verbose way to define a React component. They are the most flexible in terms of the methods you can implement on the component. They offer the most control over the component lifecycle. Class-based components have dedicated methods that are triggered during the component lifecycle.

Deprecation

Class components are still supported by React, but we don’t recommend using them in new code.

If you work on an existing code base, you might have seen them. If you are writing a new React application, it is not the preferred way to write components. However, they remain fully supported and the recommendation from the React team is to not spend time on rewriting these components unless you already had plans to rewrite them for other reasons (such as bug fixes).

class MyComponent extends React.Component<MyComponentProps> {
  render() {
    return <div>Hello World</div>;
  }
}

PureComponent

For performance reasons, you might not want your component to be re-rendered unless either state or props are changed. In this case, the PureComponent was used. Currently, better alternatives are available, but PureComponent remains available in React.

class MyComponent extends React.PureComponent<MyComponentProps> {
  render() {
    return <div>Hello World</div>;
  }
}

Stateless Function Components

Stateless function components were introduced in React 0.14. They are the most concise way to define a React component. Stateless Function components were introduced to being able to write components with a more simple syntax for components that do not have a state. Since they were introduced using arrow functions, it became a common practice to write these as such.

const MyComponent: React.FC<MyComponentProps> = (props) => {
  return <div>Hello World</div>;
};

hen TypeScript support was added, the React.SFC<T> type was introduced, later renamed to React.FC. This type helps to define the return type, and a few optional properties as well as a generic type parameter for custom properties. In React 18, the children property is removed.

interface FunctionComponent<P = {}> {
  (props: P, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P> | undefined;
  contextTypes?: ValidationMap<any> | undefined;
  defaultProps?: Partial<P> | undefined;
  displayName?: string | undefined;
}

Function-based components

Using arrow functions was a concise way to define components, however, it came with a few downsides as discussed in this pull request.

The more simple function definition using a simple type for its properties resolves these issues. This style of writing components is also used in the new React documentation as well as the preferred style in the Airbnb JavaScript Style guide.

type MyComponentProps<T> = {
  genericProp: T;
};

function MyComponent<T>(props: MyComponentProps<T>) {
  return <div>Hello World</div>;
}

function SubComponent() {
  return <p>...</p>;
}

MyComponent.SubComponent = SubComponent;

Conclusion

Historically, there have been several best ways to write React components. You likely have an existing codebase that uses a mixture of different ways to define components. It will be hard to move the legacy class-based components to function components, however, it might be feasible to go for the function declaration style for stateless components.

For new components, it does make sense to follow the style defined in the React documentation by writing components using a function definition. However, arrow functions using React.FC also remain a popular choice and are used by Vercel and the official TypeScript website

Further reading