Blog>>Software development>>Frontend>>A guide to React functional components with TypeScript

A guide to React functional components with TypeScript

This article explores various ways of typing React functional components with TypeScript, starting from basic examples and moving on to more sophisticated use cases. Read the article to discover examples of typing components, including using generic types along with dependency injection.

TypeScript benefits

TypeScript is a statically typed superset of JavaScript that adds a powerful type system to the language, enabling developers to catch type-related errors at compile time and enhance code quality. It has become a popular tool among frontend developers and for good reason. It provides a number of benefits listed below.

Services Frontend development

Static Typing

TypeScript adds a type system on top of JavaScript, allowing you to specify the types of variables, props, state, and function parameters. This helps catch type-related errors at compile time rather than runtime.

Code Auto-Completion and IntelliSense

With TypeScript, your code editor can provide better auto-completion and IntelliSense support. This means as you type, you get suggestions for available properties and methods.

Enhanced IDE Tooling

TypeScript's static typing enables IDEs to offer better tooling support. This includes refactoring tools, finding usages, and showing type-related errors directly in the editor.

Improved Collaboration

TypeScript's type annotations make the code more self-documenting and help team members or yourself understand what each functional component or function expects in the future.

Enforced Best Practices

TypeScript encourages the use of good programming practices and design patterns by providing stricter typing. This can lead to better overall code quality.

Refactoring Safety 

When refactoring or making changes to your codebase, TypeScript helps ensure that you update all affected parts of the code by notifying you of any type mismatches.

Integration with External Libraries

Many third-party libraries have TypeScript definitions available, which allows for seamless integration and better type checking when using those libraries in your React project.

If you’re interested in libraries, see our comparison of Enzyme vs. React testing libraries.

In summary, using TypeScript in React can lead to a more maintainable, reliable, and efficient codebase, making the development process smoother and more enjoyable for you and your team. However, it's essential to note that there might be an initial learning curve if you are not familiar with TypeScript. Once you become accustomed to it, the benefits will outweigh the initial investment of time and effort.

Basic components types

Let’s focus on typing the main building brick of any React application - the component. I assume you’re already familiar with the good old `FunctionalComponent` or `FC`. It’s a basic type provided by React.

If you want your typed functional component to be able to receive props, you can pass their types:

const MyComponent: FC<{stringProp: string, numberProp: number}> = ({stringProp, numberProp}) => ...

Another way is using an earlier declared interface or type alias: 

type MyComponentProps = {
  stringProp: string;
  numberProp: number;
}

const MyComponent: FC<MyComponentProps> = ({stringProp, numberProp}) => ...

Generic types

So far, so straightforward. But things can get tricky when you want to use generic types. As you might have noticed, we’ve just used them in the example above, as FC is a generic type. But let’s step back and explain the idea of generics.

In TypeScript, generic types provide a way to create reusable and flexible functional components that can work with different data types. They allow you to define functions, classes, or interfaces that can handle multiple types without specifying the exact type upfront.

The basic idea of generic types is to use placeholders (“” known as type parameters) that represent the actual types used when the function, class, or interface is invoked. These type parameters are specified between angle brackets ("<>" brackets) and can have any name, but the widespread convention is to use single capital letter. Here's a simple example to illustrate the concept:

type SelectProps<T extends string | string []> = {
  value: T;
  onChange: (newValue: T) => void;
}

When the functional component with this prop is called, TypeScript infers the type of T based on the value prop, forcing consistency with the onChange function.

In this example, the SelectProps type is generic, indicated by the syntax. The T is a type parameter, representing a placeholder for the actual type of the value prop. Using the `extends` keyword we make sure that it can be nothing that exceeds the later provided type; in this case it has to be a string or an array of strings.

Thanks to that we can further narrow the type in particular uses, i.e. using union types:

type PositionValues = 'top' | 'bottom' | 'left' | 'right'

<Select<PositionValues> {...props} />

This solution is very useful, but it makes TS scream at you every time you use `SelectProps`, demanding the types. It’s much more convenient to provide a default type, almost the same way you would with a default parameter in a function:

type SelectProps<T extends string | string[] = string> = {
  value: T;
  onChange: (newValue: T) => void;
}

Using this approach we can use both the boundaries provided by using a narrower type and have a nice default that covers most of the cases.

Overall, generic types in TypeScript are a powerful feature that allows developers to write more generic and reusable code, making the development process more efficient and maintainable.

Generic types in components

All right, that seems easy. But how do we let our component know it should accept the type parameter? The first solution I found was simply changing the arrow function to a regular one, which has no impact on its behavior in my case and the typing was easier:

function Select<T extends string | string[]>({value, onChange}: SelectProps<T>) {

...

}

There is, of course, a way to type it as an arrow function as well:

const Select = <T extends string | string[]>({value, onChange}: SelectProps<T>) => {

...

}

Generic types with dependency injection

Things got complicated again when I wanted to use generic types with a component that required dependency injection. An example of such a situation could be the use of react-ioc provider HOC and MobX observer. This article will not cover the details of the stack and implementation of this kind of solution, but let’s focus on the types:

const Select: <T extends string | string[]>({value, onChange}: SelectProps<T>) => React.ReactElement = provider(Store)(observer(() => {

...

}));

As you can see, we’re also typing the return value of the function, declaring that it’s going to be ReactElement.

Generic types in React can be a powerful tool. Remember, that you can not only use them to maintain consistency between a set of values, but also for more complex use cases.

Let’s say for our Select component we want the onChange function to differ not only by its parameter, but it should also be much deeper. We can then define two possible onChange types and pick one after we know the value type:

type SelectProps<T extends string | string[]> = {

  value: T;

  onChange: T extends string ? SingleSelectOnChange : MultiSelectOnChange;

}

Summary

In this brief article we have walked through a few ways of typing React functional components in TypeScript, summarizing the benefits of using this JavaScript superset; and not only in React applications. I hope the code snippets and examples will be useful in your development.

And a final little tip... If you ever find your generic type parameter interpreted by IDE as a JSX element you can use a trailing comma to make sure it’ll receive the right treatment, so <T, > it is. Other solutions require adding <T extends unknown / {} / any>.

Skrzeczyński Sławomir

Sławomir Skrzeczyński

Frontend Engineer

Sławomir Skrzeczyński is a frontend engineer and author on CodiLime's blog. Check out the author's articles on the blog.Read about author >

Read also

Get your project estimate

For businesses that need support in their software or network engineering projects, please fill in the form and we’ll get back to you within one business day.

For businesses that need support in their software or network engineering projects, please fill in the form and we’ll get back to you within one business day.

We guarantee 100% privacy.

Trusted by leaders:

Cisco Systems
Palo Alto Services
Equinix
Jupiter Networks
Nutanix