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.

The following errata were submitted by our customers and have not yet been approved or disproved by the author or editor. They solely represent the opinion of the customer.

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

Version Location Description Submitted by Date submitted
PDF 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
```

Kenji Imamula  Jul 22, 2024 
PDF 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 
PDF 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.

Kenji Imamula  Jul 24, 2024 
PDF 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.

Kenji Imamula  Jul 27, 2024 
PDF 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`?

Kenji Imamula  Aug 29, 2024 
PDF 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.
```

Kenji Imamula  Aug 30, 2024 
PDF 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.

Kenji Imamula  Aug 31, 2024