86 | );
87 | });
88 |
--------------------------------------------------------------------------------
/examples/safePropertyAccess.md:
--------------------------------------------------------------------------------
1 | ## Diagnostic Codes
2 |
3 | ts2339, ts2531, ts2532
4 |
5 | ## Safely Accessing Properties on Objects
6 |
7 | ```tsx
8 | declare const get: (
9 | obj: any,
10 | path: string | number | symbol,
11 | defaultValue?: any,
12 | ) => any;
13 |
14 | // go over: long vs. concise ways of dealing with deeply nested objects with conditionals
15 | interface UnreliableData {
16 | mayExist?: {
17 | deepMayExist?: {
18 | nullableString?: string | null | undefined;
19 | nullableNumber?: number | null | undefined;
20 | nullableArray?: string[] | null | undefined;
21 | nullableFunction?: (name: string) => string | null;
22 | };
23 | };
24 | }
25 |
26 | const doStuff = (data?: UnreliableData) => {
27 | // error: any nulls will cause runtime errors
28 | const unsafeNum = data.mayExist.deepMayExist.nullableNumber;
29 |
30 | // this is correct (falsy check on object, type check on primitive), but awful
31 | const num =
32 | (data &&
33 | data.mayExist &&
34 | data.mayExist.deepMayExist &&
35 | typeof data.mayExist.deepMayExist.nullableNumber === "number" &&
36 | data.mayExist.deepMayExist.nullableNumber) ||
37 | 0;
38 |
39 | // option 1: lodash get (or similar)
40 | // returns "any" and doesn't check any properties
41 | const something = get(data, "mayExist.deepMayExist.nullableNumber", 0);
42 | const notAnError = get(data, "x.y.z", "42");
43 |
44 | // full type safety and autocompletion
45 | const numberWithDefault = data?.mayExist?.deepMayExist?.nullableNumber ?? 0;
46 | const nullableNumber = data?.mayExist?.deepMayExist?.nullableNumber ?? 0;
47 | const nullableFunction = data?.mayExist?.deepMayExist?.nullableFunction?.("");
48 |
49 | // this catches properties which don't exist
50 | const intentionalError = data?.mayExist?.fdsafdsavbfd?.nullableNumber ?? 0;
51 | // and the default value needs to match the possible values
52 | // (the error message could be better here)
53 | const badDefault: number =
54 | data?.mayExist?.deepMayExist?.nullableNumber ?? "string";
55 | };
56 | ```
57 |
58 | but note that type narrowing is still necessary for union types since
59 | there's no way for typescript to differentiate between unions
60 | without your help.
61 |
62 | ```tsx
63 | interface StructureOne {
64 | deepMayExist?: {
65 | nullableString?: string | null | undefined;
66 | nullableNumber?: number | null | undefined;
67 | nullableArray?: string[] | null | undefined;
68 | nullableFunction?: ((name: string) => string) | string | null;
69 | };
70 | }
71 |
72 | interface StructureTwo {
73 | somethingElse?: {
74 | one: string;
75 | two?: number;
76 | };
77 | }
78 |
79 | interface UnreliableUnionData {
80 | mayExist?: StructureOne | StructureTwo;
81 | }
82 |
83 | const doStuffWithUnions = (data?: UnreliableUnionData) => {
84 | data?.mayExist.somethingElse; // still can't just access properties that only exist on one
85 |
86 | // need to split it up
87 | const structure = data?.mayExist;
88 | if (!!structure && "deepMayExist" in structure) {
89 | structure?.deepMayExist.nullableNumber;
90 | }
91 | // or do a type assertion (unsafe)
92 | (structure as StructureOne)?.deepMayExist.nullableNumber ?? 0;
93 | };
94 |
95 | const doStuffWithDefaults = (data: UnreliableData) => {
96 | const { deepMayExist } = data.mayExist || {};
97 | };
98 | ```
99 |
--------------------------------------------------------------------------------
/examples/widening.md:
--------------------------------------------------------------------------------
1 | # Type Widening (Quirk)
2 |
3 | ## Related Issues
4 |
5 | - https://github.com/Microsoft/TypeScript/issues/10938#issuecomment-247476364
6 | - https://github.com/microsoft/TypeScript/issues/20195
7 | - https://twitter.com/kentcdodds/status/1081333326290415618
8 |
9 | ## Typical Problem
10 |
11 | I've created an object with an exact string as one of its properties, like `"one"`. I took that and sent it to a function which expected the string to be `"one"`, and TypeScript is complaining that `Type 'string' is not assignable to type '"one"'`.
12 |
13 | ```tsx
14 | type Options = {
15 | option: "one" | "two";
16 | };
17 | const doStuff = (options: Options) => {};
18 |
19 | const options = {
20 | option: "one",
21 | };
22 | doStuff(options);
23 | ```
24 |
25 | ## Reason
26 |
27 | JavaScript has mutable objects - anything can be changed at any time. Objects defined with `const` are still allowed to have values changed at runtime. Imagine if your code looked like this:
28 |
29 | ```tsx
30 | const mutableOptions: Options = {
31 | option: "one",
32 | };
33 | // no error, but without widening, this would be:
34 | // Type 'two' is not assignable to type '"one"'
35 | mutableOptions.option = "two";
36 | ```
37 |
38 | This would be _extremely annoying_ for any code which modified the object after creation. In JS, there is a lot of code like this.
39 |
40 | ## Fixing This Error
41 |
42 | The easiest way to solve this is to avoid intermediate variables where possible. If TypeScript can figure everything out in a single expression, you won't hit this problem.
43 |
44 | ```tsx
45 | doStuff({ option: "one" });
46 | ```
47 |
48 | Of course, it's unrealistic to always put all your code on one line, so you'll still need a strategy when using one object literal in multiple places.
49 |
50 | TypeScript 3.4 added "const assertions", which can mark any property, object, or array as immutable. Adding this will prevent the value from being widened.
51 |
52 | ```tsx
53 | const singleConstOptions = {
54 | option: "one" as const,
55 | };
56 | // hey, no error!
57 | doStuff(singleConstOptions);
58 | ```
59 |
60 | You can set an entire object as immutable by adding `as const` to the whole object.
61 |
62 | ```tsx
63 | const constOptions = {
64 | option: "one",
65 | } as const;
66 | // also good
67 | doStuff(constOptions);
68 | ```
69 |
70 | If you already know the type in advance, you can also just annotate with that. Here, we already know what `Options` should be, so let's use that!
71 |
72 | ```tsx
73 | const annotatedOptions: Options = {
74 | option: "one",
75 | };
76 | // also good
77 | doStuff(annotatedOptions);
78 | ```
79 |
80 | ## Gotchas
81 |
82 | Arrays are special.
83 |
84 | The TypeScript team is opposed to major breaking changes. This results in some strange tradeoffs. If an array is marked as `readonly`, you can't send a non-`readonly` array into it.
85 |
86 | ```tsx
87 | type ImmutableStrings = readonly string[];
88 |
89 | const mutateStrings = (strings: string[]) => {};
90 |
91 | const dontMutateStrings = (strings: readonly string[]) => {};
92 |
93 | declare const immutableStrings: ImmutableStrings;
94 | declare const mutableStrings: string[];
95 |
96 | // can't send a readonly array into anything accepting non-readonly
97 | mutateStrings(immutableStrings);
98 | // non-readonly into non-readonly is fine
99 | mutateStrings(mutableStrings);
100 | // readonly and non-readonly are fine into readonly
101 | dontMutateStrings(immutableStrings);
102 | dontMutateStrings(mutableStrings);
103 | ```
104 |
105 | This does _not_ apply to properties on objects which are not arrays or tuples, despite that the same risk applies here. This is because the scale of the breaking change was much worse with properties when compared to arrays or tuples. See: https://github.com/Microsoft/TypeScript/pull/6532#issuecomment-174356151
106 |
107 | ```tsx
108 | type ImmutableObject = {
109 | readonly name: string;
110 | };
111 | type MutableObject = {
112 | name: string;
113 | };
114 | declare const immutableObject: ImmutableObject;
115 | declare const mutableObject: MutableObject;
116 | const mutateObject = (obj: MutableObject) => {
117 | obj.name = "something else!";
118 | };
119 |
120 | mutateObject(mutableObject);
121 | // surprise! readonly constraint breaks here
122 | mutateObject(immutableObject);
123 | ```
124 |
--------------------------------------------------------------------------------
/examples/excessPropertyChecking.md:
--------------------------------------------------------------------------------
1 | # Excess Property Checking (Feature)
2 |
3 | Excess Property Checking (abbreviated to EPC from here) is a system which detects when extra properties are added to objects when it's likely a mistake, while still allowing most object to contain extra properties for flexibility.
4 |
5 | At its best, this allows you to reuse the same function with very different inputs, as you define only the subset of data you need from an object. Most functions don't have a problem accepting additional properties on objects.
6 |
7 | At its worst, this is confusing or even unsafe at runtime.
8 |
9 | Knowing how this works is very important.
10 |
11 | ## Open Types
12 |
13 | All types are "open" or "inexact" in TypeScript. There currently are no
14 | "closed" or "exact" types.
15 |
16 | Here, the extra `age` property is not an error:
17 |
18 | ```tsx
19 | const getName = (person: { name: string }) => {
20 | return person.name;
21 | };
22 |
23 | const person = {
24 | name: "Rock Rockman",
25 | age: "27", // extra property
26 | };
27 | getName(person); // no error
28 | ```
29 |
30 | In many cases, this is an advantage - a function specifies what it wants from an object, which allows a single object type to work with a range of possible data. This prevents us from needing to narrow union types:
31 |
32 | ```tsx
33 | const person1 = {
34 | name: "Morgan Stryker",
35 | active: true,
36 | };
37 | const person2 = {
38 | name: "Jackson Blacklock",
39 | phone: "818-555-2000",
40 | };
41 | getName(person1);
42 | getName(person2);
43 | ```
44 |
45 | However, problems happen with "options objects", common in JS:
46 |
47 | ```tsx
48 | interface Options {
49 | firstName?: boolean;
50 | lastName?: boolean;
51 | }
52 | const getSplitName = (person: { name: string }, options: Options = {}) => {
53 | const [firstName, lastName] = person.name.split(" ");
54 | return {
55 | firstName: options.firstName ? firstName : null,
56 | lastName: options.lastName ? lastName : null,
57 | name: person.name,
58 | };
59 | };
60 |
61 | const options = {
62 | firstName: true,
63 | // "last" isn't a property, but no error is reported
64 | last: true,
65 | };
66 | getSplitName(person, options);
67 | ```
68 |
69 | To prevent this from happening, TypeScript will report errors on extra properties if the object is sent directly into a function/JSX element without being assigned to a variable:
70 |
71 | ```tsx
72 | getSplitName(person, { firstName: true, last: true }); // error!
73 | ```
74 |
75 | ## Rules of EPC
76 |
77 | From the [pull request](https://github.com/Microsoft/TypeScript/pull/3823):
78 |
79 | - Every object literal is initially considered "fresh".
80 | - When a fresh object literal is assigned to a variable or passed for a parameter of a non-empty target type, it is an error for the object literal to specify properties that don't exist in the target type.
81 | - Freshness disappears in a type assertion or when the type of an object literal is widened.
82 |
83 | If you tend to assign single-use variables before use, you may not get the benefits of EPC automatically. Where possible, it's safer and less verbose to inline options objects (or React component props) instead of assigning to a variable first.
84 |
85 | However, there are times where it makes sense, such as passing the same options / props into multiple functions or components. You can opt-in to EPC here by defining a type on the object at the time of assignment:
86 |
87 | ```tsx
88 | const nameOptions: Options = {
89 | firstName: true,
90 | last: true, // this is now checked
91 | };
92 | getSplitName(person, nameOptions);
93 | ```
94 |
95 | ## With Libraries
96 |
97 | When using a library, a couple approaches may work. Most libraries will export type definitions in addition to normal exports.
98 |
99 | ```tsx
100 | import { debounce, DebounceSettings } from "lodash";
101 |
102 | const settings: DebounceSettings = {
103 | maxWait: 30,
104 | lead: 30,
105 | };
106 | debounce(() => {}, 30, settings);
107 | ```
108 |
109 | If they don't, you can still extract specific argument types using the `Parameters<>` utility. Note that this is a last resort. For overloaded functions, the last signature defined is used. This can change from under you without warning!
110 |
111 | ```tsx
112 | type DebounceOptions = Parameters;
113 | const debounceSettings: DebounceOptions[2] = {
114 | maxWait: 4,
115 | lead: 30,
116 | };
117 |
118 | interface Overloaded {
119 | (x: number, y: number): string;
120 | (x: string): number;
121 | }
122 |
123 | interface ReversedOverloaded {
124 | (x: string): number;
125 | (x: number, y: number): string;
126 | }
127 |
128 | const param1: Parameters[0] = 1;
129 | const param2: Parameters[0] = "string";
130 | const reversedParam1: Parameters[0] = 1;
131 | const reversedParam2: Parameters[0] = "string";
132 | ```
133 |
--------------------------------------------------------------------------------
/examples/indexSignatures.md:
--------------------------------------------------------------------------------
1 | # No Index Signature Found (code ts-7053)
2 |
3 | When first trying to access a property by variable, such as `object[key]`, you're likely to hit a `No Index Signature` error when trying to work with `string`:
4 |
5 | ```tsx
6 | export {};
7 | interface Person {
8 | name: string;
9 | phone: string;
10 | }
11 | const person: Person = {
12 | name: "Rock Rockman",
13 | phone: "800-555-2400",
14 | };
15 |
16 | const get = (person: Person, key: string) => {
17 | return person[key];
18 | };
19 |
20 | get(person, "name");
21 | ```
22 |
23 | This error is useful if you're familiar with index signatures, but can misleading if you're not.
24 |
25 | ## What is an index signature?
26 |
27 | An index signature is a way of telling TypeScript "trust me, if you ask for something on this object, it will give you a specific type."
28 |
29 | Here's an example:
30 |
31 | ```tsx
32 | export {};
33 | interface TileMap {
34 | [key: string]: { item: string };
35 | }
36 | const tileMap: TileMap = {};
37 | ```
38 |
39 | These can be used alongside other keys:
40 |
41 | ```tsx
42 | export {};
43 | interface CSSProperties {
44 | color: string;
45 | [key: string]: string;
46 | }
47 | ```
48 |
49 | ## Why an index signature isn't the solution here
50 |
51 | Let's try correcting the error by adding an index signature to our `Person` type.
52 |
53 | ```tsx
54 | export {};
55 | interface Person {
56 | name: string;
57 | phone: string;
58 | [key: string]: string;
59 | }
60 | const person: Person = {
61 | name: "Big McLargeHuge",
62 | phone: "800-555-2400",
63 | };
64 |
65 | const get = (person: Person, key: string) => {
66 | return person[key];
67 | };
68 |
69 | get(person, "name");
70 | ```
71 |
72 | Hey, that worked! Looking a little closer, though... we have a problem. Now we can ask for things that don't really exist.
73 |
74 | ```tsx
75 | export {};
76 | interface Person {
77 | name: string;
78 | phone: string;
79 | [key: string]: string;
80 | }
81 | const person: Person = {
82 | name: "Big McLargeHuge",
83 | phone: "800-555-2400",
84 | };
85 |
86 | const get = (person: Person, key: string) => {
87 | return person[key];
88 | };
89 |
90 | get(person, "name");
91 | get(person, "phone");
92 | // what about other things?
93 | get(person, "pants");
94 | ```
95 |
96 | So we haven't really fixed the errors, we've just suppressed them. Instead, we need to look at the signature of `get` - specially, the `key`.
97 |
98 | ## Be More Specific
99 |
100 | We originally typed `key` as `string`. This isn't really what we want, though, as it allows any string - `a`, `pants`, or the entire text of the [complete works of Shakespeare](https://ocw.mit.edu/ans7870/6/6.006/s08/lecturenotes/files/t8.shakespeare.txt).
101 |
102 | Instead, we want to limit it just to known keys of the object. These keys are more specific than `string` - in this case, the only valid keys are `name` or `phone`. Let's start with a union type:
103 |
104 | ```tsx
105 | export {};
106 | interface Person {
107 | name: string;
108 | phone: string;
109 | }
110 | const person: Person = {
111 | name: "Big McLargeHuge",
112 | phone: "800-555-2400",
113 | };
114 |
115 | const get = (person: Person, key: "name" | "phone") => {
116 | return person[key];
117 | };
118 |
119 | get(person, "name");
120 | get(person, "phone");
121 | // good, now we get errors here
122 | get(person, "pants");
123 | ```
124 |
125 | That's better. Still, we don't want to repeat this every time. Luckily, there's a `keyof` keyword that does what we're looking for:
126 |
127 | ```tsx
128 | export {};
129 | interface Person {
130 | name: string;
131 | phone: string;
132 | }
133 | const person: Person = {
134 | name: "Big McLargeHuge",
135 | phone: "800-555-2400",
136 | };
137 |
138 | // Hover over `key` to see the reduced type: "name" | "phone"
139 | const get = (person: Person, key: keyof Person) => {
140 | return person[key];
141 | };
142 |
143 | get(person, "name");
144 | get(person, "phone");
145 | get(person, "pants");
146 | ```
147 |
148 | Great! So the problem wasn't really the lack of an index signature; our type just wasn't specific enough.
149 |
150 | Index signatures are very useful when you understand their tradeoffs, but can lead to runtime errors and poor results from refactoring tools when misused. Here are a few cases where you might want to use one:
151 |
152 | 1. You don't control an object (it comes from a backend, for instance), and there's no way to know the keys ahead of time. An example: a list of enabled/disabled features, by key/value:
153 |
154 | ```tsx
155 | export {};
156 | interface FeatureFlags {
157 | [key: string]: boolean | undefined;
158 | }
159 | ```
160 |
161 | 2. You control an object, but the keys aren't known until runtime.
162 |
163 | In this example, we might have an object of tiles in a game, where the keys correspond to `x, y` coordinates:
164 |
165 | ```tsx
166 | export {};
167 | const tileMap = {
168 | "1,1": {
169 | item: "player",
170 | },
171 | "2,2": {
172 | item: "monster",
173 | },
174 | };
175 | ```
176 |
177 | Since the player and monster move around, we couldn't know the structure of this object in advance. So, the index signature ensures we know the type of the value of an object, even if we can't know its keys.
178 |
179 | 3. You're writing to an object that will never be read by your code, and it can contain any keys. Here's a tracking call to an analytics library:
180 |
181 | ```tsx
182 | interface TrackingData {
183 | [key: string]: string | null | undefined;
184 | }
185 | declare global {
186 | interface Window {
187 | analytics: {
188 | trackEvent: (data: TrackingData) => void;
189 | };
190 | }
191 | }
192 | export {};
193 | const track = (data: TrackingData) => {
194 | return window.analytics.trackEvent(data);
195 | };
196 | ```
197 |
198 | ## Additional Reading
199 |
200 | [TypeScript Deep Dive: Index Signatures](https://basarat.gitbook.io/typescript/type-system/index-signatures)
201 |
--------------------------------------------------------------------------------
/examples/objectKeysAndEntries.md:
--------------------------------------------------------------------------------
1 | # Object.keys, Object.entries, Object.fromEntries (Tradeoff)
2 |
3 | ## How are Object.keys and Object.entries typed?
4 |
5 | `Object.keys` returns `string[]`. `Object.entries` returns `[string, Value]` (where `Value` is a union of all possible values). This works fine most of the time. However, problems happens when you're trying to transfer values from object to another by key:
6 |
7 | ```tsx
8 | export {};
9 |
10 | type FormState = {
11 | name: string;
12 | email: string;
13 | termsAccepted: boolean;
14 | };
15 | type FieldValidation = {
16 | name: boolean;
17 | email: string;
18 | termsAccepted: boolean;
19 | };
20 |
21 | declare const formState: FormState;
22 | declare const fieldValidation: FieldValidation;
23 | const valid = Object.keys(formState).forEach(key => {
24 | fieldValidation[key] = !!formState[key];
25 | });
26 | ```
27 |
28 | This error is explained at [Index Signatures](/?path=/story/examples--index-signatures)
29 |
30 | ## Why is `key` typed as `string` instead of `"name" | "age" | "termsAccepted"`?
31 |
32 | Types in TypeScript are allowed to have extra properties. See: [Excess Property Checking](/?path=/story/examples--excess-property-checking).
33 |
34 | With this in mind, typing `key` as only the _known_ keys can cause really unexpected results since you'll also be working with all the unknown ones. Here, we'll take in a `name` and `email` and use a type assertion to force the type to be `keyof FormState`:
35 |
36 | ```tsx
37 | export {};
38 |
39 | type FormState = {
40 | name: string;
41 | email: string;
42 | };
43 | type FieldValidation = {
44 | name: boolean;
45 | email: boolean;
46 | authenticated: boolean;
47 | };
48 |
49 | declare const fieldValidation: FieldValidation;
50 |
51 | const userForm = {
52 | name: "Bob Johnson",
53 | email: "bobjohnson@example.com",
54 | authenticated: true, // extra properties are allowed
55 | };
56 | const validate = (formState: FormState) => {
57 | Object.keys(formState).forEach(keyArg => {
58 | const key = keyArg as keyof FormState;
59 | // We've unexpectedly overwritten authenticated to be `true` here
60 | fieldValidation[key] = !!formState[key];
61 | });
62 | };
63 | validate(userForm);
64 | ```
65 |
66 | We've just validated someone as `authenticated` when they shouldn't be. This type of bug can be very difficult to track down.
67 |
68 | ## How likely is this to be a problem?
69 |
70 | This is completely dependent on your project. Look at where you're using `Object.keys` or `Object.entries` and try to think of ways you could end up in bad situations if extra properties are present.
71 |
72 | ## How can I avoid this?
73 |
74 | 1. Consider if you should really be using an array instead of an object. This only works if you control the structure of the data, but arrays were made for iteration and preserving order.
75 |
76 | 2. Reconsider whether iteration is even valuable. The above example written without `Object.keys` avoids any type or runtime issues:
77 |
78 | ```tsx
79 | export {};
80 |
81 | type FormState = {
82 | name: string;
83 | email: string;
84 | };
85 | type FieldValidation = {
86 | name: boolean;
87 | email: boolean;
88 | authenticated: boolean;
89 | };
90 |
91 | declare const fieldValidation: FieldValidation;
92 |
93 | const userForm = {
94 | name: "Bob Johnson",
95 | email: "bobjohnson@example.com",
96 | authenticated: true, // extra properties are OK here
97 | };
98 | const validate = (formState: FormState) => {
99 | fieldValidation.name = !!formState.name;
100 | fieldValidation.email = !!formState.email;
101 | };
102 | validate(userForm);
103 | ```
104 |
105 | 3. Accept `{ [key: string]: unknown }` in functions, which will force some extra checks for values, especially `null` and `undefined`.
106 |
107 | ## OK, I understand the risks. What if I want to just accept them and move on?
108 |
109 | To opt in to riskier but more convenient types case-by-case:
110 |
111 | ```tsx
112 | export {};
113 |
114 | const exactKeys = Object.keys as (o: T) => Extract[];
115 | const exactEntries = Object.entries as <
116 | T extends { [key: string]: any },
117 | K extends keyof T
118 | >(
119 | o: T,
120 | ) => [keyof T, T[K]][];
121 | type ValueOf = T[keyof T];
122 | const exactValues = Object.values as (
123 | o: T,
124 | ) => ValueOf[];
125 | ```
126 |
127 | To overwrite built-in definitions, making this behavior always apply:
128 |
129 | ```
130 | type ValueOf = T[keyof T];
131 |
132 | declare global {
133 | interface ObjectConstructor {
134 | // keys: (o: T) => Array>;
135 | keys(o: T): Array>;
136 | entries(
137 | o: T,
138 | ): Array<[Extract, T[K]]>;
139 | values(
140 | o: T,
141 | ): ValueOf[]
142 | }
143 | }
144 | ```
145 |
146 | ## Object.values
147 |
148 | `Object.values` implicitly returns `any` when used on an interface or type without an index signature.
149 |
150 | ```tsx
151 | interface Person {
152 | name: string;
153 | }
154 | const stringifyValues = (person: Person) => {
155 | Object.values(person).map(value => value.toString());
156 | };
157 | const person = {
158 | name: "Blast Hardpeck",
159 | authenticated: null,
160 | };
161 | stringifyValues(person); // crash on null.toString();
162 | ```
163 |
164 | ## Object.fromEntries
165 |
166 | The current definition for `Object.fromEntries` has an open issue at [#35745](https://github.com/microsoft/TypeScript/issues/35745). To opt in to more specific types when possible, add this to a `.d.ts` file in your project:
167 |
168 | ```
169 | type UnionToIntersection = (T extends T
170 | ? (p: T) => void
171 | : never) extends (p: infer U) => void
172 | ? U
173 | : never;
174 | type FromEntries = T extends T
175 | ? Record
176 | : never;
177 | type Flatten = {} & {
178 | [P in keyof T]: T[P];
179 | };
180 |
181 | declare global {
182 | interface ObjectConstructor {
183 | fromEntries<
184 | V extends PropertyKey,
185 | T extends [readonly [V, any]] | Array
186 | >(
187 | entries: T,
188 | ): Flatten>>;
189 | }
190 | }
191 | ```
192 |
193 | `Object.fromEntries` works with tuples, so you'll likely need to use `as const` to mark your return value as a tuple rather than an array.
194 |
195 | ```tsx
196 | export {};
197 |
198 | declare const people: Array<{ name: string; age: number }>;
199 | const ageMap = Object.fromEntries(
200 | people.map(person => {
201 | return [person.name, person.age] as const;
202 | }),
203 | );
204 | ```
205 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The TypeScript Error Guide
2 |
3 | TypeScript makes some normally complex tasks very easy, but it can make some seemingly-simple tasks more difficult,
4 |
5 | ## Some things that were hard: now easy!
6 |
7 | Change an API? Change the type and you'll see everywhere you need to update. Need to rename something used in 500 places? Hit F2 and rename.
8 |
9 | ## Some things that were easy: now hard.
10 |
11 | TypeScript actively discourages highly-dynamic code, redefining variables at will, and APIs that put a lot of meaning into strings. What do these look like?
12 |
13 | ```tsx
14 | const sizes = {
15 | "size-sm": 1,
16 | "size-md": 2,
17 | "size-lg": 3,
18 | };
19 |
20 | const getSize = (size: "sm" | "md" | "lg") => {
21 | return sizes[`size-${size}`];
22 | };
23 | ```
24 |
25 | This site is all about letting you know about these limitations _without_ all the type-system jargon you'll normally see.
26 |
27 | # Diagnostic Codes
28 |
29 | TypeScript has a code for every error, such as `ts(2741)`. While the error message may look very different each time, the underlying problem is the same.
30 |
31 | If you're hitting a problem, search for that code within this page.
32 |
33 | # For Site / Application Developers
34 |
35 | ## I'm trying to start with an empty object and add properties one by one, and TypeScript gives me errors no matter what I do. I've given up and `// @ts-ignore`d them.
36 |
37 | When you create an empty object, there's just no good way for TS to ensure that all properties have been added _after_ the object has already been specified. Don't worry, though - there are lots of ways to accomplish this, and one of them is very concise as well! See: [Build Up Object](examples/build-up-object.ts).
38 |
39 | Diagnostic codes: `ts(2339)`, `ts(2741)`
40 |
41 | ## What's this `never` type?
42 |
43 | This is covered in the new handbook under the [never type](https://microsoft.github.io/TypeScript-New-Handbook/chapters/more-on-functions/#never).
44 |
45 | Additionally, arrays start out as `never[]` when the type cannot be inferred. See: [Never](examples/never.ts)
46 |
47 | ## Why isn't TypeScript showing errors on extra properties I have on an object?
48 |
49 | TypeScript has a system called "excess property checking" which is a great tradeoff between type safety and developer experience... provided you know how it works. Luckily, it's pretty simple. See: [Excess Property Checking](examples/excess-property-checking.ts).
50 |
51 | ## How can I get a function to accept an object with a couple extra properties? My code works fine!
52 |
53 | Same question as above! Understanding [Excess Property Checking](examples/excess-property-checking.ts) will let you intentionally bypass this check where needed.
54 |
55 | ## I used `filter` but TS errors and says that something could be null/undefined.
56 |
57 | A nullable type is a union type, such as `string | null`. To narrow types in a function like `filter`, an extra annotation is needed for now (TypeScript might make an exception here in the future). See: [Filter and Reduce](examples/filter-reduce.ts).
58 |
59 | ## Why can't I use `reduce` without errors on the result object?
60 |
61 | You can! However, creating an empty object and building it up one property at a time isn't something that can really ever be checked for safety. A simple type assertion is typically all you need. A few different approaches are available at [Filter and Reduce](examples/filter-reduce.ts).
62 |
63 | ## I have an array of mixed object types. Why can't I narrow based on whether or not a property exists?
64 |
65 | You can! You just have to use an `in` check to opt in to less-strict checking of properties. See the tradeoffs and other solutions in [Type Guards](examples/type-guards.ts).
66 |
67 | ## I checked if something is null, and now TypeScript is complaining that something might be null.
68 |
69 | This is usually an example of TS being especially careful with callbacks, as they may or may not be called synchronously. See: [Type Widening](examples/type-widening.ts).
70 |
71 | ## Is it OK to have an object function argument with a bunch of optional properties?
72 |
73 | Maybe! If all the options really are independently optional, that's fine. However, you may have a couple properties which are _exclusive_ of one another. Think of a function that must accept either "a" or "b", but cannot accept both at once. If this covers your case, [Dependent Properties](examples/dependent-properties.ts) can help you.
74 |
75 | ## I'm using a library, and I seem to need to manually type everything for it.
76 |
77 | This can be one of a few issues.
78 |
79 | If the library you're using involves two functions that quietly pass information between each other between imports (example: redux `createStore()` and react-redux `connect()` or `useSelector()`), you might be interested in the [Extending Modules](examples/extending-modules.ts) section. This can let you define types _once_ instead of on every usage.
80 |
81 | If this isn't the case, your library is likely set up to accept generics. These are like function arguments, but for types. The syntax for these involves angle brackets (`<>`) and can be tricky, so see [Generics](examples/generics.ts) for details.
82 |
83 | Finally, some libraries just don't have very good type definitions available, or are so dynamic that creating good library definitions isn't possible, or require types which TypeScript just can't support yet. This is rare, but an example is the [humps](https://github.com/domchristie/humps) library which converts all keys of an object from snake_case to camelCase. For these instances, you might want to use a [Type Assertion](examples/type-assertion.ts). This ties into knowing [when to bail out of types](examples/when-to-bail-out.ts).
84 |
85 | ## My function accepts string | number as a type and returns string | number. I know I gave it a string but now I have to check for numbers every time.
86 |
87 | You're looking for [Generics](examples/generics.ts). These allow you to change the return value based on the arguments. Think of them like function arguments, but for types.
88 |
89 | ## Hey! I used a generic like you said, and now I'm getting type errors inside my function.
90 |
91 | Right! Generics accept anything by default, so your `add()` function suddenly needs to accept arrays, objects, and `null`. You might want to restrict which types you accept using [Bounded Type Parameters](examples/bounded-type-parameters.ts).
92 |
93 | ## I'm using Object.keys() or Object.entries() and there's some index signature error. Why is the key ending up as a `string`?
94 |
95 | This one trips up everyone at some point. There's an explanation at [Object.keys and Object.entries](examples/object-keys-and-entries.ts). Don't worry, there's a reason for it - and a few ways to opt-in to the behavior you want.
96 |
97 | ## I don't control the object being used. How do I use the `object` type?
98 |
99 | ## How can I tell TypeScript about a global variable that exists without using @ts-ignore or `any`?
100 |
101 | You're looking for the jargony `ambient context` and `ambient declaration`. When you put `declare` in front of a function, constant, or module, you are saying "trust me, this exists, and is of this type." This is especially confusing when looking at the differences between .d.ts and .ts files, so see [Types without implementations](examples/types-without-implementations.ts).
102 |
103 | # React-Specific Errors
104 |
105 | For most React errors, there's an excellect [cheat sheet](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet) for using React with TypeScript. This is updated constantly with new information.
106 |
107 | ## How do I work with `children`? All I get are errors trying to access anything.
108 |
109 | A React child is union of many, many possible things which cannot be narrowed except by complex type guards at every object depth. When iterating over children, it's probably best to use `any` and distrust the existence of any property:
110 |
111 | ```tsx
112 | const containsDiv = React.Children.toArray(children).some((child: any) => {
113 | return child?.type?.displayName === "Header";
114 | });
115 | ```
116 |
117 | ## I'm getting a "not a constructor function for JSX elements" error.
118 |
119 | You're returning something from a function component which React doesn't accept, likely `undefined`. Your code might be fine now, but if it ever hits that `undefined` case, you'll get a runtime error.
120 |
121 | ```tsx
122 | import React from "react";
123 |
124 | interface Props {
125 | text?: string;
126 | }
127 | const Button = ({ text }: Props) => {
128 | return text && ;
129 | };
130 | ```
131 |
132 | If `text` isn't supplied to Button, it'll return the current value of `text` (which is `undefined`). React currently throws in this case.
133 |
134 | To get a better error message, add a return type annotation to your function component of either `ReactElement` or `ReactElement | null`. This will move the error to the return statement:
135 |
136 | ```tsx
137 | interface Props {
138 | text?: string;
139 | }
140 | const Button = ({ text }: Props): ReactElement | null => {
141 | return text ? : null;
142 | };
143 | ```
144 |
145 | # For Library Authors
146 |
147 | ## I want to make my library work with TypeScript. What's all this .d.ts nonsense?
148 |
149 | d.ts files are interesting. They allow you to declare the external API for your library without needing to specify
150 |
151 | This can be challenging.
152 |
153 | ## How do I test types?
154 |
155 | If you're writing library definitions, [Testing Types](examples/testing-types.ts) can help you, by using the [Conditional Type Checks](https://github.com/dsherret/conditional-type-checks) library.
156 |
--------------------------------------------------------------------------------