Explained: 'React' refers to a UMD global
Find out why this error occurs and learn how to fix it.
Since I first got into advanced TypeScript, I've been in love with a particular pattern. It formed the basis for one of my first-ever TypeScript tips, and it's been extraordinarily useful to me ever since.
I call it the IIMT (rhymes with 'limped'): the Immediately Indexed Mapped Type.
Here's what it looks like:
typescript
type SomeObject = {a: string;b: number;};/*** | {* key: 'a';* }* | {* key: 'b';* }*/export type Example = {[K in keyof SomeObject]: {key: K;};}[keyof SomeObject];
Before we discuss what's happening, let's look at the structure. We first create a mapped type:
typescript
/*** {* a: {* key: 'a';* },* b: {* key: 'b';* }* }*/export type Example = {[K in keyof SomeObject]: {key: K;};};
This mapped type iterates over the keys of SomeObject
and creates a new object type for each key. In this example, we're creating a new object type with a single property, key
, whose value is the key of the object.
We then immediately index into this mapped type with keyof SomeObject
, which is a | b
. This means that the resulting type is the union of all the values of the mapped type.
typescript
/*** | {* key: 'a';* }* | {* key: 'b';* }*/export type Example = {[K in keyof SomeObject]: {key: K;};}[keyof SomeObject];
There you have it - we first create the mapped type, then immediately index into it: an IIMT.
IIMTs give us a really clear model for iterating over members of a union while also preserving the context of the entire union. Let's say we want to create a discriminated union based on a union of strings:
typescript
type Fruit = "apple" | "banana" | "orange";/*** | {* thisFruit: 'apple';* allFruit: 'apple' | 'banana' | 'orange';* }* | {* thisFruit: 'banana';* allFruit: 'apple' | 'banana' | 'orange';* }* | {* thisFruit: 'orange';* allFruit: 'apple' | 'banana' | 'orange';* }*/export type FruitInfo = {[F in Fruit]: {thisFruit: F;allFruit: Fruit;};}[Fruit];
We can see that the resulting type is a union of three objects, each with a thisFruit
property and an allFruit
property. The thisFruit
property is the specific member of the union, and the allFruit
property is the entire union.
This lets us do really smart things within the scope where F
is defined. What if we wanted to capture the other fruit?
typescript
/*** | {* thisFruit: 'apple';* allFruit: 'banana' | 'orange';* }* | {* thisFruit: 'banana';* allFruit: 'apple' | 'orange';* }* | {* thisFruit: 'orange';* allFruit: 'apple' | 'banana';* }*/export type FruitInfo = {[F in Fruit]: {thisFruit: F;allFruit: Exclude<Fruit, F>;};}[Fruit];
Because F
and Fruit
are available in the same closure, we can use Exclude
to remove the current fruit from the union. Very nice - and once you're used to the IIMT structure, pretty clear to read.
IIMTs are also useful for transforming unions of objects. Let's say we have a union of objects, and we want to change a property to each object:
typescript
type Event =| {type: "click";x: number;y: number;}| {type: "hover";element: HTMLElement;};
This might look like it doesn't fit our IIMT model. If we try to create a mapped type with Event
, we'll get an error:
typescript
type Example = {// Type 'Event' is not assignable to// type 'string | number | symbol'.[E in Event]: {};};
That's because we can't create a mapped type out of something that isn't a key. But, fortunately, we can use as
inside our mapped type to make it work:
typescript
/*** PrefixType takes an object with a 'type' property* and prefixes the type with 'PREFIX_'.*/type PrefixType<E extends { type: string }> = {type: `PREFIX_${E["type"]}`;} & Omit<E, "type">;/*** | {* type: 'PREFIX_click';* x: number;* y: number;* }* | {* type: 'PREFIX_hover';* element: HTMLElement;* }*/type Example = {[E in Event as E["type"]]: PrefixType<E>;}[Event["type"]];
Here, we insert the as E['type']
to remap the key to the type we want. We then use PrefixType
to prefix the type
property of each object.
Finally, we immediately index into the mapped type using Event['type']
, which is click | hover
- so we end up with a union of the prefixed objects.
Let's tie this off by looking at a couple of examples:
typescript
type CSSUnits = "px" | "em" | "rem" | "vw" | "vh";/*** | {* length: number;* unit: 'px';* }* | {* length: number;* unit: 'em';* }* | {* length: number;* unit: 'rem';* }* | {* length: number;* unit: 'vw';* }* | {* length: number;* unit: 'vh';* }*/export type CSSLength = {[U in CSSUnits]: {length: number;unit: U;};}[CSSUnits];
typescript
type SuccessResponseCode = 200;type ErrorResponseCode = 400 | 500;type ResponseCode =| SuccessResponseCode| ErrorResponseCode;/*** | {* code: 200;* body: {* success: true;* };* }* | {* code: 400;* body: {* success: false;* error: string;* };* }* | {* code: 500;* body: {* success: false;* error: string;* };* }*/type ResponseShape = {[C in ResponseCode]: {code: C;body: C extends SuccessResponseCode? { success: true }: { success: false; error: string };};}[ResponseCode];
Share this article with your friends
Find out why this error occurs and learn how to fix it.
Learn the differences between React.ReactNode
and JSX.Element
in TypeScript when working with React.
There are three rules to keep in mind when deciding where to put types in your application code.
Discover the power of ComponentProps in React and TypeScript. Learn how to extract and utilize props from native HTML elements, existing components, and elements with associated refs. Enhance your development workflow with this essential type helper.
Testing types is a crucial aspect of developing libraries in TypeScript. In this article, we explore three different ways to test your types: using the vitest test runner, rolling your own solution with type helpers, and leveraging the tsd library.
The TypeScript 5.1 beta is out - here's everything you need to know.
There’s a difference between using TypeScript and knowing TypeScript.
The docs give you a good grasp of the pieces like generic functions, conditional types, and type helpers.
But out in the wild, developers are combining these pieces together into patterns.
Four of the most important patterns to know and use are:
Testing code doesn't need to be typed so strictly, and sometimes tests need to pass the wrong type. I made a library called shoehorn that eases the pain of working with tests in TypeScript by providing a first-class way to pass the wrong type to a function.
The article discusses why TypeScript does not throw an error when a function that is assigned to a variable doesn't match its type. It explains that a function with fewer parameters than its type can still be passed, and this behavior is not restricted to TypeScript but exists in JavaScript as well.
TypeScript 5.0 introduces const type parameters which are useful in preserving the literal types of objects passed to functions.
Updates to TypeScript 5.0 have made their way into Total TypeScript!
Exclude
is a very powerful utility type that can be used in a variety of ways. In this article, I'll show you 9 ways to use it along with code examples.
As a frontend developer, your job isn't just pixel-pushing. Most of the complexity in frontend comes from handling all the various states your app can be in.
It might be loading data, waiting for a form to be filled in, or sending a telemetry event - or all three at the same time.
If you aren't handling your states properly, you're likely to come unstuck. And handling states starts with how th
Using the satisfies keyword is one of four ways to make type assignments in TypeScript. In this article we'll look at examples of when each method should be used.
Understand why TypeScript throws complicated errors by learning how to read them. Errors mirror the structure of the code being compared and can be simplified by changing the way types are assigned.
Learn how to use TypeScript generics on the type level and with functions to improve code readability, type safety, and reduce repetitive code. Use "type helpers" to create new types and generic functions to pass in and return specific types.
Use Zod to validate unknown inputs in your app, whether it's a CLI or a public API, to ensure that data entering your app is safe. Zod can also be used for 'sort of' trusted inputs, like third-party services, to ensure that the data returned is what you expect.
TypeScript's template literal syntax allows developers to manipulate and transform strings in a powerful way. This can be extended using unions, infer, and recursion to handle more complex tasks.
Donny (kdy1 on GitHub) is rewriting TypeScript in Rust hoping to speed up tsc which is slow due to its TypeScript base. In this interview, he explains some of the financial and technical challenges they face with this open-source project.
Let's imagine you're creating a function which sums up an array of objects. Here's one, taken from the Excalidraw codebase:
const sum = <T>(array: readonly T[], mapper: (item: T) => number): number => array.reduce((acc, item) => acc + mapper(item), 0)
Let's look at the type definition. This function takes in:
readonly T[]