Add TypeScript To An Existing React Project
Learn how to add TypeScript to your existing React project in a few simple steps.
If you don't understand generics in TypeScript, I think there's something you've misunderstood.
There is no such thing as a 'generic'.
There are generic types, generic functions, and generic classes.
There are type arguments and type parameters.
You cannot 'pass' a generic, 'declare' it, or 'infer' it.
In other words, 'generic' is not a noun, it's an adjective.
People think of 'a generic' as something in TypeScript.
You might look at the code below and say 'we're passing a generic to useState'.
ts
import {useState } from "react";useState <string>();
You might also say 'we're passing two generics to Record
':
ts
typeNumberRecord =Record <string, number>;
How about 'Maybe
has two generics'?
ts
typeMaybe <T > =T | null | undefined;
People look at the angle bracket syntax and think 'that's a generic'. But because generics can appear on functions, function calls, types, and type declarations, it's not clear what 'a generic' even is.
That's why it's such a hard concept for folks to grasp - the word is too overloaded.
So, what terms should we use instead?
How would we describe this code if we couldn't use the word 'generic'?
ts
import {useState } from "react";useState <string>();
We're not passing a 'generic' to useState
. We are passing it a type argument. And the type argument we're passing is string
.
How about Record
?
ts
typeNumberRecord =Record <string, number>;
We're passing two type arguments to Record
. The first type argument is string
, and the second type argument is number
.
So - a type argument works just like a function argument. We can pass it to a function, class, or type.
But not all types, functions, and classes can receive type arguments:
ts
typeType 'PropertyKey' is not generic.2315Type 'PropertyKey' is not generic.Example =PropertyKey <string>;Expected 0 type arguments, but got 1.2558Expected 0 type arguments, but got 1.encodeURIComponent <string >();newExpected 0 type arguments, but got 1.2558Expected 0 type arguments, but got 1.Event <string >();
So how do we know which ones can receive them?
Let's look at our Maybe<T>
from earlier.
ts
typeMaybe <T > =T | null | undefined;
Here, Maybe
is declaring a type parameter. The type parameter is T
.
This means that Maybe
MUST be passed a type argument. If we don't pass it a type argument, we get an error:
ts
typeGeneric type 'Maybe' requires 1 type argument(s).2314Generic type 'Maybe' requires 1 type argument(s).Example =; Maybe
So, a type parameter is like a function parameter. It declares that you can pass a type argument to the type, function, or class.
Let's bring the phrase 'generic' back into our vocabulary and give it a proper definition.
generic - adj: a type, function, or class that declares one or more type parameters.
So, Maybe
is a generic type because it declares a type parameter.
And PropertyKey
, which we saw earlier, is NOT generic. Even the error says so.
ts
typeType 'PropertyKey' is not generic.2315Type 'PropertyKey' is not generic.Example =PropertyKey <string>;
So, generic types are simply types that declare type parameters.
Functions and classes can also declare type parameters. When they do, they become generic functions and generic classes and can receive type arguments.
ts
constmyFunc = <T >() => {// implementation...};myFunc <string>();classMyClass <T > {// implementation...}newMyClass <string>();
This can be used in all sorts of ways. By far the most popular is to provide type information to third-party libraries. In the example below, how would useState
know what type it's supposed to be returning?
ts
import {useState } from "react";const [message ,setMessage ] =useState ();
It can't - so we have to pass a type argument to it.
ts
import {useState } from "react";const [message ,setMessage ] =useState <string>();
You'll notice, though, that generic functions and generic classes act differently from types. They don't require you to pass a type argument.
ts
// No error!myFunc ();newMyClass ();
But generic types do require you to pass one.
ts
typeGeneric type 'Maybe' requires 1 type argument(s).2314Generic type 'Maybe' requires 1 type argument(s).Example =; Maybe
Why is this? Well, if you don't pass a type argument to a generic function or generic class, it'll attempt to infer it from the runtime arguments.
This is how useState
works under the hood. Its declaration looks something like this (simplified):
ts
declare functionuseState <T >(initial ?:T ): [T , (newValue :T ) => void];
You can see that it accepts T
as a type parameter and returns a tuple containing T
and a function to update it.
If we pass a runtime argument to it, TypeScript can infer the type argument from that:
ts
// T is inferred as string!const [message ,setMessage ] =useState ("Hello!");// T is inferred as number!const [id ,setId ] =useState (1);
Now that we're passing a runtime argument, TypeScript can infer the type argument from it.
Remove the noun 'generic' from your vocabulary. Replace it with 'type argument' and 'type parameter'. Use it as an adjective only.
I'll give you one concession - the plural, 'generics'. It's too widely ingrained to be gotten rid of.
Let's keep it - but let's consider it a useful shorthand for 'generic types', 'generic functions', and 'generic classes'.
Share this article with your friends
Learn how to add TypeScript to your existing React project in a few simple steps.
Learn the essential TypeScript configuration options and create a concise tsconfig.json file for your projects with this helpful cheatsheet.
Big projects like Svelte and Drizzle are not abandoning TypeScript, despite some recent claims.
Learn different ways to pass a component as a prop in React: passing JSX, using React.ComponentType, and using React.ElementType.
Learn about TypeScript performance and how it affects code type-checking speed, autocomplete, and build times in your editor.
When typing React props in a TypeScript app, using interfaces is recommended, especially when dealing with complex intersections of props.