Errata

Effective TypeScript

Errata for Effective TypeScript, Second Edition

Submit your own errata for this product.

The errata list is a list of errors and their corrections that were found after the product was released. If the error was corrected in a later version or reprint the date of the correction will be displayed in the column titled "Date Corrected".

The following errata were submitted by our customers and approved as valid errors by the author or editor.

Color key: Serious technical mistake Minor technical mistake Language or formatting error Typo Question Note Update

Version Location Description Submitted By Date submitted Date corrected
Page P239 (Item 54)
Code snippet after the third paragraph

This may be nitpicky, but the type of a value returned from `objectToCamel` is not accurate when it is called with an object with `number` keys:

```ts
const camel = objectToCamel({foo_bar: 12, 1: 'a'});
// camel's value at runtime is {fooBar: 12, 1: 'a'},
// while its type here is {fooBar: 12}
```

The accurate definition of `ObjectToCamel` should be:

```ts
type ObjectToCamel<T extends object> = {
[K in keyof T as K extends string
? ToCamel<K> : K extends number
? K : never]: T[K]
};
```

Here's the full example code to see the issue (I tried to provide a link to TS Playground but this form doesn't allow me to do so...):

```ts
type ToCamel<S extends string> =
S extends `${infer Head}_${infer Tail}`
? `${Head}${Capitalize<ToCamel<Tail>>}`
: S;

type ObjectToCamel<T extends object> = {
[K in keyof T as ToCamel<K & string>]: T[K]
};
function camelCase(term: string) {
return term.replace(/_([a-z])/g, m => m[1].toUpperCase());
}
function objectToCamel<T extends object>(obj: T): ObjectToCamel<T> {
const out: any = {};
for (const [k, v] of Object.entries(obj)) {
out[camelCase(k)] = v;
}
return out;
}

const camel = objectToCamel({ foo_bar: 12, 1: 'a' });
// camel's value at runtime is {fooBar: 12, 1: 'a'},
// while its type here is {fooBar: 12}
console.log(camel.fooBar); // 12
console.log(camel[1]); // this should not raise an error

type ObjectToCamelImproved<T extends object> = {
[K in keyof T as K extends string
? ToCamel<K> : K extends number
? K : never]: T[K]
};

const camel2 = (objectToCamel as <T extends object>(obj: T) => ObjectToCamelImproved<T>)({ foo_bar: 12, 1: 'a' });
console.log(camel2.fooBar); // 12
console.log(camel2[1]); // a
```

Note from the Author or Editor:
This is definitely nitpicky :)

This is so in the weeds and the workaround is so cumbersome (double conditional) that I wouldn't want to add anything more than a caveat like "making this work with numeric index types is left as an exercise to the reader."

Note that Kenji's version, which explicitly checks for `number`, is better than `K in keyof T as K extends string ? ToCamel<K> : K` since that would allow Symbol keys to pass through, whereas at runtime the `objectToCamel` drops these (since `Object.entries` does).

Kenji Imamula  Jul 22, 2024 
Page P242 (Item 55)
5th paragraph

> a function type is assignable to another function type that takes fewer parameters:

This is not true. "fewer" should be replaced with "more":

> a function type is assignable to another function type that takes more parameters:

Kenji Imamula  Jul 23, 2024 
Page Page 251 (Item 56)
Last paragraph

> Adding this special case does not change the behavior of PartiallyPartial at all

This is not correct. Because the condition of the version of PartiallyPartial on page 250 is not distributed over unions, it behaves differently than that on page 251 when `T` is a union type:

```ts
type Resolve<T> = T extends Function ? T : { [K in keyof T]: T[K] };
type PartiallyPartialP250<T, K extends keyof T> =
Resolve<Partial<Pick<T, K>> & Omit<T, K>>;
type PartiallyPartialP251<T extends object, K extends keyof T> =
[K] extends [never]
? T // special case
: T extends unknown // extra conditional to preserve distribution over unions
? Resolve<Partial<Pick<T, K>> & Omit<T, K>>
: never;
interface BlogComment {
commentId: number;
title: string;
content: string;
}
interface VlogComment {
commentId: number;
time: number;
content: string;
}
type PartCommentP250 = PartiallyPartialP250<BlogComment | VlogComment, 'content'>;
// ^? type PartCommentP250 = {
// content?: string | undefined;
// commentId: number;
// }
type PartCommentP251 = PartiallyPartialP251<BlogComment | VlogComment, 'content'>;
// ^? type PartCommentP251 = {
// content?: string | undefined;
// commentId: number;
// title: string;
// } | {
// content?: string | undefined;
// commentId: number;
// time: number;
// }
```

The `T extends unknown` part must either be removed from the page 251 version or added to the page 250 version to make the description correct.

Note from the Author or Editor:
The text is technically correct in that "Adding this special case" on its own does not change the behavior of `PartiallyPartial`.

Distributing over unions _is_ a behavior change but it's brought about by the other change (adding the `T extends unknown ? ...` clause). This is an unrelated but still desirable change.

The only part I find objectionable is the comment:

```ts
: T extends unknown // extra conditional to preserve distribution over unions
```

The word "preserve" suggests that this was the old behavior, which it was not. The comment should be changed to:

```ts
: T extends unknown // extra conditional to distribute over unions
```

Kenji Imamula  Jul 24, 2024 
Page Page 275 (Item 62)
2nd code snippet

```ts
function buildURL<Path extends keyof RouteQueryParams>( route: Path,
...args: (
RouteQueryParams[Path] extends null ? []
: [params: RouteQueryParams[Path]]
) ){
const params = args ? args[0] : null;
return route + (params ? `?${new URLSearchParams(params)}` : ''); }
```

The use of the ternary operator is unnecessary because `args` is non-null.

Note from the Author or Editor:
Correct, the ternary can be removed because `args` will always be an array, and in JS a zero-length array is still truthy.

- const params = args ? args[0] : null;
+ const params = args[0];

Kenji Imamula  Jul 27, 2024 
Page Page 18 (Item 4), Page 278 (Item 63), and Page 279 (Item 64)
The last code snippet on Page 18 and the first code snippet on Page 278 and Page 279

`calculateLength` on Page 18, `norm` on Page 278, and `calculateNorm` on Page 279 all have identical function bodies, though their names are different. Only `calculateLength` describes what it does correctly. Should they be all named `calculateLength`?

Note from the Author or Editor:
They should all have the same name. `calculateLength` is best since "norm" is a somewhat math-y term.

Kenji Imamula  Aug 29, 2024 
Page Page 81 (Item 16)
2nd paragraph from last (the description for "A type for the key").

> Typically it’s string or a subtype of string such as a union of string literals.

A string literal or a union of string literals cannot be used for a type for the key in index signatures.

```ts
type Rocket = {[property: 'foo' | 'bar']: string};
```

The above code results in the following error:

```
An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
```

Note from the Author or Editor:
It can be a template literal type, which can be a proper subtype of string, but it can't be a string literal type or union thereof. The bullet point should mention template literal types.

Kenji Imamula  Aug 30, 2024 
Page Page 126 (Item 26)
2nd code snippet

Although the sentence above the code snippet says, "Here’s the equivalent with Lodash:" it's not equivalent to the previous code snippet because of `.slice(0, 10)` at last.

Note from the Author or Editor:
The `.slice(0, 10)` line should be dropped from the second example.

Kenji Imamula  Aug 31, 2024