- Published on
Typescript Types Cookbook
- Authors
- Name
- Justin D Vrana
This post contains a continually updated list of handy Typescript recipes. These have been written, curated, and edited by me from various projects over the years.
Enjoy!
Simple types
/**
* Unpack an inner type such as an Array<T>
*/
export type Unpacked<T> = T extends Array<infer U> ? U : T
/**
* Recursive array (e.g. for ReactChildren)
*/
export type RecursiveArray<T> = Array<T | RecursiveArray<T>>
export type ReactChildrenOfType<P> = RecursiveArray<React.ReactElement<P>> | React.ReactElement<P>
/**
* Ensures an array type has at least one element.
* @example
* let validArray: NonEmptyArray<number> = [1]; // Valid
* let invalidArray: NonEmptyArray<number> = []; // Error
*/
export type NonEmptyArray<T> = [T, ...T[]];
/**
* Makes selected properties of an object type optional.
* @example
* interface Example { a: number; b: string; }
* type ExamplePartial = PartialBy<Example, 'b'>; // { a: number; b?: string; }
*/
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
/**
* Extracts the keys of an object where the property is required (not undefined).
* @example
* interface Example { a: number; b?: string; }
* type ExampleRequiredKeys = RequiredKeys<Example>; // 'a'
*/
export type RequiredKeys<T> = { [K in keyof T]-?: undefined extends T[K] ? never : K }[keyof T];
/**
* Converts a readonly structure into a mutable one.
* @example
* const config: Writable<Readonly<{ a: number }>> = { a: 5 };
* config.a = 10; // Now possible
*/
export type Writable<T> = { -readonly [P in keyof T]: T[P] };
/**
* Makes every nested property of an object readonly.
* @example
* const nested: DeepReadonly<{ a: { b: number } }> = { a: { b: 1 } };
* nested.a.b = 2; // Error
*/
export type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> };
/**
* Extracts the type wrapped in a Promise.
* @example
* async function getValue() { return 5; }
* type Value = PromiseType<ReturnType<typeof getValue>>; // number
*/
export type PromiseType<T extends Promise<any>> = T extends Promise<infer U> ? U : never;
/**
* Flattens nested types into a single level object type.
* @example
* type Nested = { a: { b: number } };
* type Flat = Flatten<Nested>; // { a: { b: number } }
*/
export type Flatten<T> = { [P in keyof T]: T[P] extends infer U ? U : T[P] };
/**
* Extracts the argument types from a function as a tuple.
* @example
* function example(x: number, y: string): void {}
* type Args = FunctionArgs<typeof example>; // [number, string]
*/
export type FunctionArgs<T extends Function> = T extends (...args: infer U) => any ? U : never;
/**
* Extracts the return type of a function.
* @example
* function sum(a: number, b: number): number { return a + b; }
* type SumResult = FunctionReturnType<typeof sum>; // number
*/
export type FunctionReturnType<T extends Function> = T extends (...args: any[]) => infer U ? U : never;
/**
* Implements a type-level conditional expression.
* @example
* type IsString<T> = If<T extends string, 'Yes', 'No'>;
* type Test = IsString<string>; // 'Yes'
* type Test2 = IsString<number>; // 'No'
*/
export type If<Condition extends boolean, True, False> = Condition extends true ? True : False;
Types of paths, keys, or attributes
/**
* Recursively get type of all keys
*/
export type RecursiveKeyOf<TObj extends object> = {
[TKey in keyof TObj & (string | number)]:
TObj[TKey] extends any[] ? `${TKey}` :
TObj[TKey] extends object
? `${TKey}` | `${TKey}.${RecursiveKeyOf<TObj[TKey]>}`
: `${TKey}`;
}[keyof TObj & (string | number)]
/**
* Returns an interface stripped of all keys that don't resolve to U, defaulting
* to a non-strict comparison of T[key] extends U. Setting B to true performs
* a strict type comparison of T[key] extends U & U extends T[key]
*/
export type KeysOfType<T, U, B = false> = {
[P in keyof T]: B extends true
? T[P] extends U
? (U extends T[P]
? P
: never)
: never
: T[P] extends U
? P
: never;
}[keyof T]
/**
* Returns an interface stripped of all properties that do not resolve to U, defaulting
* to a non-strict comparison of T[key] extends U. Setting B to true performs
* a strict type comparison of T[key] extends U & U extends T[key]
* For example, you may want to pick all type properties of type
*/
type PickByType<T, U, B = false> = Pick<T, KeysOfType<T, U, B>>
type OmitByType<T, U, B = false> = Omit<T, KeysOfType<T, U, B>>
/**
* Constructs a string literal that represents a path by joining two segments with a dot ('.') if the second segment is not empty.
* This type utility is used to concatenate path segments in a type-safe manner, ensuring that segments are only joined if both are strings or numbers.
*
* @typeparam K The first path segment, which must be a string or number.
* @typeparam P The second path segment, which must be a string or number.
* @returns A string literal representing the joined path. If `P` is an empty string, `K` is returned without a dot. Otherwise, `K` and `P` are joined by a dot.
*
* @example
* type Path = Join<'a', 'b'>; // Result: 'a.b'
* type SimplePath = Join<'a', ''>; // Result: 'a'
*/
export type Join<K, P> = K extends string | number ?
P extends string | number ?
`${K}${'' extends P ? '' : '.'}${P}`
: never : never
/**
* A utility type used to decrement a numeric type within other type definitions, primarily used in recursive type aliases to manage depth.
* The type effectively maps a number to its predecessor, with a defined mapping for values from 0 to 20 and a fallback to `never` for deeper recursion.
*
* @returns The predecessor of a given index number within the range of 0 to 20, with `never` used for indices outside this range.
*
* @example
* type Zero = Prev[1]; // Result: 0
* type OutOfBounds = Prev[21]; // Result: never
*/
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...Array<0>]
/**
* Generates a union of string literals representing all possible leaf paths through an object's properties up to a specified depth `D`.
* This type recursively explores each branch of the object to the specified depth, capturing the paths that lead to leaf nodes (end points).
* It is particularly useful for type-safe access to nested properties and for generating exhaustive lists of paths to leaf nodes.
*
* @typeparam T The object type to generate leaf paths from.
* @typeparam D The maximum depth to traverse within the object, defaulting to 10.
* @returns A union of string literals, each representing a path to a leaf property within object `T`.
*
* @example
* interface Example {
* a: {
* b: {
* c: number;
* };
* d: string;
* };
* }
*
* // Type would be "a.b.c" | "a.d"
* type ExampleLeaves = Leaves<Example>;
*/
export type Leaves<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: Join<K, Leaves<T[K], Prev[D]>> }[keyof T] : ''
/**
* Generates a union of string literals representing all possible paths through an object's properties up to a
* specified depth. Will recursively traverse up to depth `D`, defaulting to 10 levels deep.
*
* @typeParam T The object type to generate paths from.
* @typeParam D The maximum depth to traverse within the object, defaulting to 10.
* @returns A union of string literals, each representing a path to a property within object `T`.
*
* @example
* interface Example {
* a: {
* b: {
* c: number;
* };
* d: string;
* };
* }
*
* // Type would be "a" | "a.b" | "a.b.c" | "a.d"
* type ExamplePaths = Paths<Example>;
*/
export type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{
[K in keyof T]-?: K extends string | number ?
`${K}` | Join<K, Paths<T[K], Prev[D]>>
: never
}[keyof T] : ''
Specialty Types
GraphQL for Mocked Types in Storybook
These types are for a fairly specific situation of mocking graphql responses for Storybook testing.
The finals results are fully-typed mocked graphql data for your Storybook stories. Pretty useful.
Here is the file structure for this example:
src
|── types.ts
|── gqlhub
| |── queries.ts
|── utils
| |── types.ts
| |── testing.utils
|── components
| |── FooComponent
| | |── FooReactComponent.tsx
| | |── FooReactComponent.stories.tsx
| | |── mocks.ts
types.ts
import { TypedDocumentNode } from '@graphql-typed-document-node/core'
export type ResultOfGraphQLNode<Type> = Type extends TypedDocumentNode<infer X> ? X : never──
export type VarsOfGraphQLNode<Type> = Type extends TypedDocumentNode<any, infer X> ? X : never
utils/testing.utils
import { ApolloError } from '@apollo/client'
import { MockedResponse } from '@apollo/client/testing'
import { ResultOfGraphQLNode, VarsOfGraphQLNode } from 'types'
/**
* A ApolloClient mock type
*
* @example
* const mocks: Array<Mock<typeof QUERY_LIST_EXPERIMENT>> = []
*/
export interface ApolloClientMock<T> {
request: {
query: T
variables?: VarsOfGraphQLNode<T>
}
result: {
data: ResultOfGraphQLNode<T> | Generator<ResultOfGraphQLNode<T>>
loading?: boolean
}
error?: ApolloError
delay?: number
}
// export type ApolloClientMockArray<T> = Array<ApolloClientMock<T>>
export type ApolloClientMockArray<T> = Array<MockedResponse<ResultOfGraphQLNode<T>>>
mocks.ts
import { ApolloClientMockArray } from 'utils/testing.utils'
import { GET_FOO_QUERY } from '../queries'
type MockType = ApolloClientMockArray<typeof GET_FOO_QUERY>
const mocks: MockType = [
{
request: {
query: GET_FOO_QUERY
},
result: {
data: {
queryResults: {
__typename: 'FooResult',
results: [
{
__typename: 'FileUserAssociation',
name: 'foo1'
},
{
__typename: 'FileUserAssociation',
name: 'foo2'
},
]
}
}
}
}
]
export default mocks
And finally, using the mocks in a storybook:
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import FooReactComponent from './FooReactComponent'
import { MockedProvider } from '@apollo/client/testing'
import mocks from './story.data'
const meta: Meta<typeof FooReactComponent> = {
title: 'Tutorial/FooReactComponent',
component: FooReactComponent
}
export default meta
type Story = StoryObj<typeof FooReactComponent>
/*
*👇 Render functions are a framework specific feature to allow you control on how the component renders.
* See https://storybook.js.org/docs/react/api/csf
* to learn how to use render functions.
*/
export const Default: Story = {
args: {},
render: (args) => <div>
<MockedProvider mocks={mocks} >
<ExperimentExplorer/>
</MockedProvider>
</div>
}