Add TypeScript To An Existing React Project
Learn how to add TypeScript to your existing React project in a few simple steps.
The satisfies
operator gives you a way to add type annotations to values without losing the inference of the value.
If you want to learn more, I've got a deep dive in this article.
But here, let's dive deep into some use cases.
satisfies
is great for strongly typing functions that usually take a much looser type.
When you're working with URLSearchParams
, it usually takes a Record<string, string>
as its argument. This is a very loose type and doesn't enforce any particular keys.
But usually, you're creating some search params to pass them to a URL. So the loose type ends up being quite dangerous.
satisfies
to the rescue. You can use it inline to strongly type the params object.
ts
typeGHIssueURLParams = {title : string;body : string;};constparams = newURLSearchParams ({title : "New Issue",}Type '{ title: string; }' does not satisfy the expected type 'GHIssueURLParams'. Property 'body' is missing in type '{ title: string; }' but required in type 'GHIssueURLParams'.1360Type '{ title: string; }' does not satisfy the expected type 'GHIssueURLParams'. Property 'body' is missing in type '{ title: string; }' but required in type 'GHIssueURLParams'.satisfies GHIssueURLParams );consturl = `${GITHUB_REPO }?${params }`;
Here, we're getting an error saying we've got a missing property body
. This is great because it means we can't accidentally create a URL without a body.
When making POST requests, it's important to send the correct data structure to the server. The server will expect a specific format for the request body, but the process of turning it into JSON with JSON.stringify
removes any strong typings.
But with the satisfies
operator, we can strongly type it.
ts
typePost = {title : string;content : string;};fetch ("/api/posts", {method : "POST",body :JSON .stringify ({title : "New Post",content : "Lorem ipsum.",} satisfiesPost ),});
Here, we can annotate the request body with the Post
type, ensuring that the title and content properties are present and of the correct type.
as const
with satisfiesOften, you'll want to declare an array of elements in TypeScript, but have it inferred as a tuple, not an array.
Normally, you would use the as const
assertion to infer a tuple type instead of an array type. However, with the satisfies
operator, you can achieve the same result without using as const
.
ts
// @errors: 2493// @noUncheckedIndexedAccess: truetype MoreThanOneMember = [any, ...any[]];const array = [1, 2, 3];// ^?const maybeExists = array[3];// ^?const tuple = [1, 2, 3] satisfies MoreThanOneMember;// ^?const doesNotExist = tuple[3];
In the code above, we're declaring an array two different ways. If we don't annotate it with satisfies
, it gets inferred as number[]
. This means when we try to access an element on it that doesn't exist, TypeScript doesn't give us an error; it just infers it as number | undefined
.
However, when we declare tuple
using the satisfies
operator, it infers the type as a tuple with exactly three elements. Now, when we try to access the fourth element with tuple[3]
, TypeScript correctly gives us an error because the index is out of bounds.
as const
object to be a certain shape with satisfiesWhen using as const
, we can specify that an object should be treated as an immutable value with literal types. However, this doesn't enforce any specific shape or properties for the object. To enforce a certain shape for an as const
object, we can leverage the satisfies
operator.
In the example below, we have a RouteObject
type that represents a collection of routes. Each route has a url
property of type string
, and an optional searchParams
property. We want to ensure that our routes
object satisfies this RouteObject
type.
ts
typeRouteObject =Record <string,{url : string;searchParams :Record <string, string>;}>;constroutes = {home : {url : "/",searchParams : {},},about : {url : "/about",},} asType '{ readonly home: { readonly url: "/"; readonly searchParams: {}; }; readonly about: { readonly url: "/about"; }; }' does not satisfy the expected type 'RouteObject'. Property 'about' is incompatible with index signature. Property 'searchParams' is missing in type '{ readonly url: "/about"; }' but required in type '{ url: string; searchParams: Record<string, string>; }'.1360Type '{ readonly home: { readonly url: "/"; readonly searchParams: {}; }; readonly about: { readonly url: "/about"; }; }' does not satisfy the expected type 'RouteObject'. Property 'about' is incompatible with index signature. Property 'searchParams' is missing in type '{ readonly url: "/about"; }' but required in type '{ url: string; searchParams: Record<string, string>; }'.const satisfies RouteObject ;
Not only does this give us great errors in the case of a missing property, but it also gives autocomplete for the routes
object.
as const
array to be a certain shape with satisfiesUsing satisfies
, as const
, and arrays together can be a little tricky.
Let's take an example where we have a navigation menu that consists of elements with a title, an optional URL, and an optional array of nested navigation elements under the children
property.
ts
typeNavElement = {title : string;url ?: string;children ?: readonlyNavElement [];};constnav = [{title : "Home",url : "/",},{title : "About",children : [{title : "Team",url : "/about/team",},],},] asconst satisfies readonlyNavElement [];
Now, if we try to access a property that is not part of the defined shape, TypeScript will give us an error.
ts
Property 'children' does not exist on type '{ readonly title: "Home"; readonly url: "/"; }'.2339Property 'children' does not exist on type '{ readonly title: "Home"; readonly url: "/"; }'.nav [0].; children
readonly
arrays with satisfiesIt's important to note the use of readonly
on the arrays. Without the one on children
, TypeScript errors:
ts
typeNavElement = {title : string;url ?: string;children ?:NavElement [];};constnav = [{title : "About",children : [],},] asType 'readonly [{ readonly title: "About"; readonly children: readonly []; }]' does not satisfy the expected type 'readonly NavElement[]'. Type '{ readonly title: "About"; readonly children: readonly []; }' is not assignable to type 'NavElement'. Types of property 'children' are incompatible. The type 'readonly []' is 'readonly' and cannot be assigned to the mutable type 'NavElement[]'.1360Type 'readonly [{ readonly title: "About"; readonly children: readonly []; }]' does not satisfy the expected type 'readonly NavElement[]'. Type '{ readonly title: "About"; readonly children: readonly []; }' is not assignable to type 'NavElement'. Types of property 'children' are incompatible. The type 'readonly []' is 'readonly' and cannot be assigned to the mutable type 'NavElement[]'.const satisfies readonlyNavElement [];
This is because NavElement[]
is mutable, so it needs to be marked with readonly
to match up with as const
.
The same is true if we miss off the final readonly
:
ts
typeNavElement = {title : string;url ?: string;children ?: readonlyNavElement [];};constnav = [{title : "About",children : [],},] asType 'readonly [{ readonly title: "About"; readonly children: readonly []; }]' does not satisfy the expected type 'NavElement[]'. The type 'readonly [{ readonly title: "About"; readonly children: readonly []; }]' is 'readonly' and cannot be assigned to the mutable type 'NavElement[]'.1360Type 'readonly [{ readonly title: "About"; readonly children: readonly []; }]' does not satisfy the expected type 'NavElement[]'. The type 'readonly [{ readonly title: "About"; readonly children: readonly []; }]' is 'readonly' and cannot be assigned to the mutable type 'NavElement[]'.const satisfies NavElement [];
This is because our outer type, NavElement[]
, is mutable and so not assignable to the readonly as const
declaration.
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.