coryhouse / coryhouse/reactjsconsulting
TypeScript
- Dominant language
- JavaScript
- Stars
- 374
- Forks
- 33
- PR merge metrics
- No merged PRs in 30d
Description
[10 JS/TS features I avoid](https://twitter.com/housecor/status/1644326515348258818)
## Tips
**1. Think [in sets](https://blog.thoughtspile.tech/2023/01/23/typescript-sets/).**
- Every type is a Set of values.
- Some Sets are infinite: string, object; some finite: boolean, undefined.
- unknown is Universal Set (including all values), while never is Empty Set (including no value).
- The & operator creates an Intersection. It creates a smaller set. It must have both.
- The | operator creates a Union: a larger Set but potentially with fewer commonly available fields (if two object types are composed).
**2. Understand declared type and narrowed type**
- A variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type. You can narrow a type by checking values.
**3. Use a discriminated union instead of optional fields**
```ts
type Circle = { kind: 'circle'; radius: number };
type Rect = { kind: 'rect'; width: number; height: number };
type Shape = Circle | Rect;
```
Above, the kind property lets us discriminate. This gives us more type safety than merely using optional fields.
**4. Use a type predicate to avoid type assertion**
```ts
function isCircle(shape: Shape): shape is Circle {
return shape.kind === 'circle';
}
```
## tsconfig settings checklist for ideal safety
- Enable strict mode
- Enable noFallthroughCasesInSwitch
- Enable noUncheckedIndexedAccess some argue [it should be enabled by default](https://github.com/microsoft/TypeScript/issues/49169)
- Set `allowJs` false
- Enable forceConsistentCasingInFileNames
## Key Blog posts, tweets, sites
- [Style guide](https://mkosir.github.io/typescript-style-guide/)
- [react with typescript course by Matt Pocock](https://www.totaltypescript.com/tutorials/react-with-typescript)
- [Code smells](https://twitter.com/housecor/status/1568631460306817024) - Also using `!` is a smell
- [15 common errors that TS makes impossible](https://twitter.com/housecor/status/1462855012384657416)
- [6 ways to narrow types](https://www.carlrippon.com/6-ways-to-narrow-types-in-typescript/)
- [My take on TS vs PropTypes](https://twitter.com/housecor/status/1236272660264431616)
- [Common struggles getting started](https://twitter.com/jsjoeio/status/1193605374768603138?s=21)
- [Video that contrasts using React with and without TypeScript](https://www.youtube.com/watch?v=8AViJ-MeCbw)
- [Moving an existing JS project to TS](https://www.twilio.com/blog/move-to-typescript)
- [Generate propTypes from TypeScript via Babel Plugin](https://github.com/milesj/babel-plugin-typescript-to-proptypes)
- [Generate TypeScript types from C#](http://type.litesolutions.net/)
- [TSdx - Build a lib with TS](https://github.com/palmerhq/tsdx)
- [Using TS with React](https://simonknott.de/articles/Using-TypeScript-with-React.html)
- [Event types](https://www.totaltypescript.com/event-types-in-react-and-typescript)
- [Using TS with Redux](https://github.com/piotrwitek/react-redux-typescript-guide)
- [TypeScript FSA](https://github.com/aikoven/typescript-fsa)
- [A simple discriminated union example](https://www.typescriptlang.org/play?#code/C4TwDgpgBA8gTgEwnKBeKBvAUFXUB2AhgLYQBcUAzsHAJb4DmOeSlAxnWMLQPb4D8FanUbNchYBQAyPNhN74sAXygAyKAApseKsAkBXShQDkcCIQQhjyqAB9MY3QaNRj9AApweDM5UrWdCAAPSDZgCAQAEQgAG1oAN2QQCkiJCBt7bTxqZxM2HmIwGIhwgLxg0PCo2ISklLTHJDjEswQYfHrw5QBKLCwAM318MIUoBhKAUWJCWhiAWQg-QnGNHkRkCngkOG6HHUoAd1pgNgALTTXtgDoc4ENdrJ1cOUpoU3NLYzJHJ7wzO7g+CgAAMACQYS7IK5EUgqWiUKASKDgyFwK4SJTAvq-Z6EV6uDxeHyLfzfHFPf76QEglHrNEwiAqI4xGJQABG0CatVayIhdKuFQgYQi0WaSUx2JxLze+UKxVKZPJfxKVKBYL51wZKlOePZEAgQK5LQiiOAvNRVyNyAi7QlOiUyiAA) and a [longer post on the topic by Davidkpiano](https://dev.to/davidkpiano/redux-is-half-of-a-pattern-1-2-1hd7)
- [Exhaustiveness checks via exceptions](https://2ality.com/2020/02/typescript-exhaustiveness-checks-via-exceptions.html)
- [Generate runtime types from TypeScript](https://github.com/vedantroy/typecheck.macro) - Useful for runtime type checks on data returned from an HTTP call (remember, TS doesn't run at runtime)
- [Create CommonJS packages with TS](https://2ality.com/2020/04/npm-cjs-typescript.html)
- [Fully typed web apps](https://www.epicweb.dev/fully-typed-web-apps) by Kent C. Dodds
- [Error handling via a BaseError](https://twitter.com/housecor/status/1612222617141878784)
- [Opaque/Nominal/Branded types](https://twitter.com/mattpocockuk/status/1625173884885401600) - Create named types that represent value types like `number` or `string`. And easy with [Zod via .brand](https://github.com/colinhacks/zod#brand). Also see [ts-brand](https://github.com/kourge/ts-brand)
## Avoid Enums
Prefer [string literal types](https://stackoverflow.com/a/59420158) or [const objects](https://www.youtube.com/watch?v=jjMbPt_H3RQ&t=318s) over enums. [String literal types video](https://www.youtube.com/watch?v=Anu8vHXsavo)
- Enums bloat the bundle because [they generate a reverse mapping](https://ultimatecourses.com/blog/const-enums-typescript) from the enum values to the enum names.
- Enums cause confusion because they're the one TS feature that manifests at runtime and uses nominal typing instead of structural typing.
## d.ts files
- Only use d.TS files for js files. Avoid otherwise, since doing so creates a global type.
- Set skipLibCheck true to improve perf. Shouldn’t need anyway.
## Handling [Boundaries](https://www.epicweb.dev/fully-typed-web-apps)
Boundaries:
-Local storage
-User input
-Network
-Config-based or Conventions
-File system
-Database requests
### Ways to handle boundaries
1. Write type guards/type assertion functions. To avoid having to write type guards by hand, Zod, [Valibot](https://valibot.dev/), [tiny-invariant](https://npm.im/tiny-invariant), [typia](https://typia.io/docs/validators/is/#performance), and [many others](https://valibot.dev/guides/comparison/).
1. Use tools that generate types. GraphQL CodeGen, Prisma, Open API, tRPC
1. Inform TS of your convention/configuration - Tanstack Router
## Convert a large existing project from JS to TS via a script:
https://github.com/airbnb/ts-migrate
## Cheat sheets
- [React TypeScript Cheat sheet](https://react-typescript-cheatsheet.netlify.app/)
- [React DefinitelyTyped repo](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L2633-L2750)
- [React Types cheat sheet](https://www.saltycrane.com/cheat-sheets/typescript/react/latest/)
- [TS Types cheat sheet](https://www.saltycrane.com/cheat-sheets/typescript/latest/)
- [React component patterns](https://fettblog.eu/typescript-react-component-patterns)
- [Use with React Query](https://tkdodo.eu/blog/react-query-and-type-script)
- [How to type a React from submit handler](https://epicreact.dev/how-to-type-a-react-form-on-submit-handler/)
## Utility libraries
[Pattern matching via ts-pattern](https://github.com/gvergnaud/ts-pattern)
[type-fest](https://github.com/sindresorhus/type-fest)
[ts-extras](https://github.com/sindresorhus/ts-extras)
[tiny-invariant](https://www.npmjs.com/package/tiny-invariant) - Throw an error if something unexpected occurs.
## TypeScript with Node
1. [bun.sh](https://bun.sh/) - fastest, but still experimental
1. With esbuild: [esbuild-runner](https://github.com/folke/esbuild-runner), [tsup](https://github.com/egoist/tsup), [esbuild-register](https://github.com/egoist/esbuild-register), [tsm](https://github.com/lukeed/tsm).
1. With swc: [ts-node with swc](https://typestrong.org/ts-node/docs/transpilers/#swc) - About as fast as esbuild, but a little more boilerplate to setup.
1. [ts-node-dev](https://www.npmjs.com/package/ts-node-dev) - Same as ts-node, but restarts faster
Boilerplates:
[swc with Node boilerplate](https://github.com/kaleabmelkie/node-starter)
https://github.com/reconbot/typescript-library-template
## Generators for mocking/ testing
- [ ] [My simple test builder pattern example](https://www.typescriptlang.org/play?#code/PTAEBUFMGcBdQEYFcCWAbAJpATqADgIayw4B2AUCKAIJrQD2o0kkoAFsXtAFwiwCeeGAGNsKPLAC0sDBgB0AcxSw2SBHJT1gsaJIJJY9SQFt6wgNZgAZvVwFQaFAlCHQBaNBRxQAd2VsXNi9ycgEhUABVZlwAXlAAb3JQUCsUbDgAOQJjSG4mWDFSBQAaJId3WCycvLhCkrLIYwJ0GoKUItLkqhQmpVJWAFYABgBqUFNsVjxseiFsWBQYdhxIYqZGZV96JExEVntJgjR2d2g0VldRSCJ90H6fUHa4AlJhVnbQa+EAkjg5cgAvgBuEJUAAikGEaAIk1AWCs+jQ8AAbkckEsbLgvgEkNFQLjIBhHqQXDAdORhPRSN54YjYFA4FEcHkmbEEmVUulKtlcqAAEQAYVs-D5nXKmR5eT5AAltsxRQ0mi1+ZTsPwAAKHYSwABW0Ep1J2CyKckpxj5ZSoQp2RNpRrcaGO9BUOFAwzG01mOAWS0M9EBIMoYAZ8DYkDQcxSSFeCypgSI+OY0ECrDtSJDrLcyfsz3m7QU+Ho7VgayDiAMbmEbwk2fwMIWR0ieMxoHoyJwYgw+bhkARRug-ys0e1mhJCkg9LJrIAFIQ80dWXkAAr1lBHAA8rIAfABKBKgMqTWBIbAk+KgOSXtOTxnRNaXuRzhtoVnAwGgsACl6Ji5BZNUyBJEcfpcA+X4dBcRgAEdUAsNB+FAcd4G-J5YBeN4UlsUk4HzNY-BUdYch7PskX-dtsE7LAKANbxwIANTRVg4iQjNomnc9OQlap+SFNUACY+QBHcQRo+hzjkNB6AUad6MY4TQCoAAZKTkxdYi6VYnA1mQeB8ICTjuSI5h4FcXj+D44k4GueQgA)
- [ ] [TS Auto Mock](https://typescript-tdd.github.io/ts-auto-mock/) - Create / Generate mocks for any type
- [ ] [Fishery Factory library](https://thoughtbot.com/blog/announcing-fishery-a-javascript-and-typescript-factory-library)
- [ ] [jest-mock-extended](https://github.com/marchaos/jest-mock-extended)
# Books
- https://basarat.gitbook.io/typescript/
- https://www.amazon.com/Effective-TypeScript-Specific-Ways-Improve/dp/1492053740
- https://www.amazon.com/Programming-TypeScript-Making-JavaScript-Applications/dp/1492037656/
# Websites / Repos
https://github.com/mdevils/typescript-exercises
## TypeScript Tips
### Validate children
```ts
const allowedChildren = ["string", "span", "em", "b", "i", "strong"];
function isSupportedElement(child: React.ReactElement) {
return (
allowedChildren.some((c) => c === child.type) || ReactIs.isFragment(child)
);
}
// Only certain child elements are accepted. Recursively check child elements to assure all elements are supported.
function validateChildren(children: React.ReactNode) {
return React.Children.map(children, (child) => {
if (!React.isValidElement(child)) return child;
const elementChild: React.ReactElement = child;
if (child.props.children) validateChildren(elementChild.props.children);
if (!isSupportedElement(elementChild)) {
throw new Error(
`Children of type ${
child.type
} aren't permitted. Only the following child elements are allowed in Inline Alert: ${allowedChildren.join(
", "
)}`
);
}
return elementChild;
});
}
```
[Map JSON strings to native JavaScript dates](https://twitter.com/tsimbalar/status/1139639082777292800)
[Type your data as close to the source as possible](https://twitter.com/t3dotgg/status/1556539631323078657), or better yet, use [Zod](https://blog.logrocket.com/comparing-schema-validation-libraries-zod-vs-yup/) to validate if on the server (Zod is a bit heavy for the client). Zod is especially helpful for validating and Zod schemas can easily be converted into TS types via `infer`. [My Sandbox with a Zod format function](https://codesandbox.io/s/zod-format-example-1nfsmn?file=/src/index.ts)
[Strongly type env vars using Zod](https://www.typescriptlang.org/play?#code/PTAEDEEMGsFNQJYFsAOB7ATgF1FtoBzWHAASwE8VYBnYAOzQBNYAoZdbUFSLAC1ABmGNElABybnzEs2qTDgDeoAF6gAvoOGixyptJYBjNHWo5YdAG4A1SBgSQARgBsaoALwqAdGgcArWAZYABQKLKDhAMoA8gCyAKIAKgASAJIAcgDiAPoAwlFRADIAXF6mdnQEQQCULGo1LObWtvbONJ7cGNSwQSjCBjTUno31zAZOtvAETj6QTqCh4aB0kEg03P2gaUywAFIR82GL4SCgAArCVNjk4tHxyenZeYVioGgCuJTwYnQArkgOsAwLwQ1CWaBwkGo1AQBGWrUORxOeHEZQQFWBdGYAA8PlQUVhygRQAAfUA-TGwARo2CMMSeBGLNFYQECSAbc5ofpQuKWUCwLHMzGg5SeNECQEAHgoVDefMsNjsjhc1AAfPM1AiNRrDMZqGgXJ5ppVepyBkNLJ5bolUplcvkClUgA). And [use types to disable process.env](https://twitter.com/mattpocockuk/status/1782333031518220643)
React's ref has 2 types: `React.ObjectRef` (which has a current prop, and is normally what you'll want), and `React.RefCallback` which is for callback functions. The shorter, `React.Ref` is the broader type that covers both, so that's rarely useful.
[Prefer `unknown` over `any`](https://stackoverflow.com/a/51439876/26180). How to read: `unknown` is I don’t know. `any` is I don’t care. So we should favor saying “I don’t know”, rather than “I don’t care”. Because “I don’t know” means, when you work with this, you need to narrow the type.
[Why I don't use React.FC](https://fettblog.eu/typescript-react-why-i-dont-use-react-fc)
### "Extend" without an interface using an intersection type. But AVOID THIS because [it's slow](https://twitter.com/housecor/status/1781415502767673464):
```ts
export type ButtonProps = React.ButtonHTMLAttributes & {
/** Adds a class to the root element of the component */
className?: string;
}
```
### Strongly typed keys via Object.keys
```ts
// Helper to get strongly typed keys from object via https://stackoverflow.com/questions/52856496/typescript-object-keys-return-string
const getKeys = Object.keys as (obj: T) => Array;
```
### Default Props and destructuring
```ts
type LoginMsgProps = {
name?: string;
};
function LoginMsg({ name = "Guest" }: LoginMsgProps) {
return
Logged in as {name}
;}
```
### WithChildren helper type:
```ts
type WithChildren =
T & { children?: React.ReactNode };
type CardProps = WithChildren<{
title: string;
}>;
```
### Clone `React.ReactNode`
```ts
function getIcon() {
if (React.isValidElement(icon)) {
React.cloneElement(icon, { className: "extra-class"});
}
}
```
### Support spreading props or using a computed property
```ts
[x: string]: any;
```
### Use a type to declare a union type
Given a type like this
```js
export const icons = {
arrowDown: {
label: "Down Arrow",
data() {
return
}
},
arrowLeft: {
label: "Left Arrow",
data() {
return
}
},
...
}
```
You can declare a type like this:
```ts
export type IconName = keyof typeof icons
```
Think of this as "whenever something has the type "IconName", it must be a string that matches one of the keys of the icons object." [More on why this works](https://twitter.com/jamonholmgren/status/1288512765078847488).
### Declare a prop on a native HTML element as required
First, create a helper:
```ts
type MakeRequired = Omit &
Required<{ [P in K]: T[P] }>;
```
Then, use it:
```ts
type ImgProps
= MakeRequired<
JSX.IntrinsicElements["img"],
"alt" | "src"
>;
export function Img({ alt, ...allProps }: ImgProps) {
return ;
}
const zz = ;
```
### Remove a prop and declare it differently
```ts
type ControlledProps =
Omit & {
value?: string;
};
```
### Spread attributes to HTML elements
```ts
type ButtonProps = JSX.IntrinsicElements["button"];
function Button({ ...allProps }: ButtonProps) {
return ;
}
```
### Omit a type
```ts
type ButtonProps =
Omit;
function Button({ ...allProps }: ButtonProps) {
return ;
}
// 💥 This breaks, as we omitted type
const z = Hi;
```
[Conditional React Props](https://www.benmvp.com/blog/conditional-react-props-typescript/) (Use TS to only allow specific combinations of related props) - And an [excellent video](https://www.youtube.com/watch?v=vXh4PFwZFGI)
```ts
interface CommonProps {
children: React.ReactNode
// ...other props that always exist
}
type TruncateProps =
| { truncate?: false; showExpanded?: never }
| { truncate: true; showExpanded?: boolean }
type Props = CommonProps & TruncateProps
const Text = ({ children, showExpanded, truncate }: Props) => {
// Both truncate & showExpanded will be of
// the type `boolean | undefined`
}
```
[Single onChange handler](https://typeofnan.dev/a-react-typescript-change-handler-to-rule-them-all/)
```ts
const onUserChange =
(prop: P, value: User[P]) => {
setUser({ ...user, [prop]: value });
};
```
## Derive union from array
```ts
const list = ['a', 'b', 'c'] as const;
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';
```
## Testing via Jest
Test utils:
```ts
// IMPORTANT: Import this file BEFORE the thing you want to mock.
import * as fetchModule from "node-fetch";
jest.mock("node-fetch");
const fetchModuleDefault = fetchModule.default as unknown as jest.Mock;
// Disables console logging. Useful for tests that call
// code that outputs to the console, so we don't litter the test
// output with needless console statements.
export function disableConsoleLog() {
jest.spyOn(console, "warn").mockImplementation();
jest.spyOn(console, "info").mockImplementation();
jest.spyOn(console, "log").mockImplementation();
jest.spyOn(console, "error").mockImplementation();
}
beforeEach(() => {
fetchModuleDefault.mockClear();
});
afterEach(() => {
jest.restoreAllMocks();
});
// Useful for complete control
export const mockFetch = (fn: any = () => null) => {
fetchModuleDefault.mockImplementation(jest.fn(fn));
return fetchModuleDefault;
};
type MockFetchResponse = {
responseJson?: any;
status?: number;
};
// Convenient when you just want to specify the response
export const mockFetchResponse = ({
responseJson = "",
status = 200,
}: MockFetchResponse) => {
return mockFetch(async () => ({
ok: status >= 200 && status < 300,
status,
json: async () => responseJson,
}));
};
// Via https://instil.co/blog/typescript-testing-tips-mocking-functions-with-jest/
export function mockFunction any>(
fn: T
): jest.MockedFunction {
return fn as jest.MockedFunction;
}
```
[Template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) - Useful when you want to use a template string with some placeholders to declare a union of string literal types. Yes, you could generate the list manually (and you should if the list is huge), but this can be handy when the size is reasonable.
## Advanced TypeScript
The pivotal moment of transitioning from intermediate to advanced TypeScript was realizing [the type system is a programming language in itself, w/ variables, functions, conditionals, & loops](https://twitter.com/flybayer/status/1565061070363959297). Utility types like Required, Record, Pick, Omit are all built with these primitives.
Generics parameterize types like functions parameterize value. [Generics overview from Matt](https://twitter.com/mattpocockuk/status/1625838626742435842)

This table is a summary of [Type Programming](https://www.zhenghao.io/posts/type-programming)
Operation | JS | TS |
|----|----- | ---- |
| Variable | var/const | Generic |
| Conditional | == | Conditional types - [`extends` with ternary](https://www.zhenghao.io/posts/type-programming#equality-comparisons-and-conditional-branching) |
| List | Array | Union Types |
| Prop access | obj.prop or obj["prop"] | [type["prop"]](https://www.zhenghao.io/posts/type-programming#retrieve-types-of-properties-by-indexing-into-object-types) |
| Map | obj.map | [[K in keyof T]](https://www.zhenghao.io/posts/type-programming#map-and-filter) |
| Filter | obj.filter | [`as` with `never`](https://www.zhenghao.io/posts/type-programming#map-and-filter) |
| Pattern matching | regex | [`infer`](https://www.zhenghao.io/posts/type-programming#pattern-matching) |
| Loops | forEach, recursion, map | [recursively call type](https://www.zhenghao.io/posts/type-programming#recursion-instead-of-iteration) |
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.