Skip to content

Instantly share code, notes, and snippets.

@TCotton
Last active November 29, 2025 10:40
Show Gist options
  • Select an option

  • Save TCotton/ce390fda3eaae533fc3780876669862c to your computer and use it in GitHub Desktop.

Select an option

Save TCotton/ce390fda3eaae533fc3780876669862c to your computer and use it in GitHub Desktop.
TypeScript cheatsheet
// return types from functions
type P = ReturnType<typeof dateFromUUIDv7>;
// return type with promise
type P = Awaited<ReturnType<typeof dateFromUUIDv7>>;
// see all types
type PrettifyPromise<T> = {
[K in keyof T]: T[K] extends Promise<infer U> ? U : T[K];
} & {}
type Prettify<T> = {
[K in keyof T]: T[K]
} & {}
type idk = Prettify<DateFromUUIDv7Result>
// safe typescrip way of creating unknown Record
export type UnknownRecord = Record<PropertyKey, unknown>;
//This type signature Buffer & { length: 16 } is too strict for practical use.
// While it might seem like a good way to enforce 16-byte buffers at compile-time,
// TypeScript's type system cannot verify at compile-time that a dynamically
// created Buffer will have exactly 16 bytes. This type only works if you explicitly
// type-assert every buffer as having length: 16, which defeats the purpose of type safety.
// note on static check
// this is permissable but unadvisabe
type UuidBuffer = Buffer & { length: 5 };
// It creates a refined (narrower) type by taking the existing Buffer type
// and intersecting it with an additional constraint that says length must be exactly 16.
// However, this is NOT
type StringCheck = String & { length: 5 };
// TypeScript does not expose a length property as a literal type on primitive string,
// while it does on tuples and on certain narrowed arrays.
// So you can write it, but it won’t actually narrow at compile time.
// This is the only way to check for string length. This checks for five characters
// This checks for a fixed length array
type FixedLengthArray<T, N extends number> = N extends N ? number extends N ? Array<T> : _FixedLengthArray<T, N, []>
: never
type _FixedLengthArray<T, N extends number, R extends Array<unknown>> = R['length'] extends N ? R
: _FixedLengthArray<T, N, [T, ...R]>
type Array16<T> = FixedLengthArray<T, 16>
type Hash256Bytes = Array16<number>
const anArray = [1, 2, 3]
// for the same reasons above, this permissible but unadvisable
function takesHash256Bytes(input: Hash256Bytes = anArray): Hash256Bytes {
return input
}
// Pair the above TS with a runtime type guard:
function isArray16<T>(arr: Array<T>): arr is Array16<T> {
return arr.length === 16
}
// Using Generic Context to Avoid Distributive Conditional Types
type Fruit = "apple" | "banana" | "orange";
type GetAppleOrBanana<T> = T extends "apple" | "banana" ? T : never
type AppleOrBanana = GetAppleOrBanana<Fruit>
type tests = [Expect<Equal<AppleOrBanana, "apple" | "banana">>];
// safe typescrip way of creating unknown Record
export type UnknownRecord = Record<PropertyKey, unknown>;
// type narrowing with Record<PropertyKey, unknown>
// Source - https://stackoverflow.com/a/79489937
// Posted by jcalz
// Retrieved 2025-11-15, License - CC BY-SA 4.0
const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => {
if (
typeof variable !== "object" ||
Array.isArray(variable) ||
variable === null
) {
throw new Error("Not Record");
}
return variable as Record<PropertyKey, unknown>
};
const parseAsRecord = (variable: Record<PropertyKey, unknown> | string | number | boolean | bigint | null | undefined | unknown[]) => {
if (
typeof variable !== "object" ||
Array.isArray(variable) ||
variable === null
) {
throw new Error("Not Record");
}
return variable;
};
parseAsRecord('string');
parseAsRecord(null);
parseAsRecord({});
parseAsRecord([])
const testingFrameworks = {
vitest: {
label: "Vitest",
},
jest: {
label: "Jest",
},
mocha: {
label: "Mocha",
},
};
// Create Unions from Objects Using Two Operators
type k = keyof typeof testingFrameworks;
// take parameters from function
const makeQuery = (
url: string,
opts?: {
method?: string;
headers?: {
[key: string]: string;
};
body?: string;
}
) => {};
type MakeQueryParameters = Parameters<typeof makeQuery>;
export type Event =
| {
type: "click";
event: MouseEvent;
}
| {
type: "focus";
event: FocusEvent;
}
| {
type: "keydown";
event: KeyboardEvent;
};
// Extract From A Union Using a Utility Type
type ClickEvent = Extract<Event, { type: "click" }>
export type Event =
| {
type: "click";
event: MouseEvent;
}
| {
type: "focus";
event: FocusEvent;
}
| {
type: "keydown";
event: KeyboardEvent;
};
// Use a Utility Type to Remove a Single Member of a Union
type NonKeyDownEvents = Exclude<Event, { type: "keydown" }>
export const fakeDataDefaults = {
String: "Default string",
Int: 1,
Float: 1.14,
Boolean: true,
ID: "id",
};
// Use Indexed Access Types to Extract Object Properties
type FakeDataDefaults = typeof fakeDataDefaults
export type StringType = FakeDataDefaults["String"]
export type IntType = FakeDataDefaults["Int"]
export type FloatType = FakeDataDefaults["Float"]
export type BooleanType = FakeDataDefaults["Boolean"]
export type IDType = FakeDataDefaults["ID"]
// Simple Syntax Used to Access Parts of a Discriminated Union
type EventType = Event["type"];
export const programModeEnumMap = {
GROUP: "group",
ANNOUNCEMENT: "announcement",
ONE_ON_ONE: "1on1",
SELF_DIRECTED: "selfDirected",
PLANNED_ONE_ON_ONE: "planned1on1",
PLANNED_SELF_DIRECTED: "plannedSelfDirected",
} as const;
export type IndividualProgram = typeof programModeEnumMap[
| "ONE_ON_ONE"
| "SELF_DIRECTED"
| "PLANNED_ONE_ON_ONE"
| "PLANNED_SELF_DIRECTED"
]
export type IndividualProgram = typeof programModeEnumMap[
Exclude<
keyof typeof programModeEnumMap,
"GROUP" | "ANNOUNCEMENT"
>
]
const frontendToBackendEnumMap = {
singleModule: "SINGLE_MODULE",
multiModule: "MULTI_MODULE",
sharedModule: "SHARED_MODULE",
} as const;
// Use Two Operators With Indexed Access to Get All of an Object's Values
type BackendModuleEnum =
typeof frontendToBackendEnumMap[keyof typeof frontendToBackendEnumMap]
const fruits = ["apple", "banana", "orange"] as const;
// Methods Used to Create Unions out of Array Values
type AppleOrBanana = typeof fruits[0 | 1];
type Fruit = typeof fruits[number];
// use template literals
type Route = `/${string}`
export const goToRoute = (route: Route) => {};
// Extracting String Pattern Matches with Template Literals
type Routes = "/users" | "/users/:id" | "/posts" | "/posts/:id";
type DynamicRoutes = Extract<Routes, `${string}:${string}`>
// Passing Unions Into Template Literals
type BreadType = "rye" | "brown" | "white";
type Filling = "cheese" | "ham" | "salami";
type Sandwich = `${BreadType} sandwich with ${Filling}`;
type tests = [
Expect<
Equal<
Sandwich,
| "rye sandwich with cheese"
| "rye sandwich with ham"
| "rye sandwich with salami"
| "brown sandwich with cheese"
| "brown sandwich with ham"
| "brown sandwich with salami"
| "white sandwich with cheese"
| "white sandwich with ham"
| "white sandwich with salami"
>
>
];
// Manipulate String Literals Using Type Helpers - to uppercase
type Event = `log_in` | "log_out" | "sign_up";
type ObjectOfKeys = Record<Uppercase<Event>, string>;
type tests = [
Expect<
Equal<
ObjectOfKeys,
{
LOG_IN: string;
LOG_OUT: string;
SIGN_UP: string;
}
>
>
// Use Constraints to Limit Type Parameters
type AddRoutePrefix<TRoute extends string> = `/${TRoute}`;
type tests = [
Expect<Equal<AddRoutePrefix<"">, "/">>,
Expect<Equal<AddRoutePrefix<"about">, "/about">>,
Expect<Equal<AddRoutePrefix<"about/team">, "/about/team">>,
Expect<Equal<AddRoutePrefix<"blog">, "/blog">>,
// @ts-expect-error
AddRoutePrefix<boolean>,
// @ts-expect-error
AddRoutePrefix<number>,
];
// Add Support for Multiple Types in a Type Helper
type CreateDataShape = {
data: unknown;
error: unknown;
};
type CreateDataShape<TData, TError> = {
data: TData;
error: TError;
};
//S et a Default Type Value in a Type Helper
type CreateDataShape<TData, TError extends Error = undefined>
data: TData;
error: TError;
};
// Support Function Type Constraints with Variable Arguments
type GetParametersAndReturnType<T extends () => any> = {}
type tests = [
Expect<
Equal<
GetParametersAndReturnType<() => string>,
{ params: []; returnValue: string }
>
>,
Expect<
Equal<
GetParametersAndReturnType<(s: string) => void>,
{ params: [string]; returnValue: void }
>
>,
Expect<
Equal<
GetParametersAndReturnType<(n: number, b: boolean) => number>,
{ params: [number, boolean]; returnValue: number }
>
>,
];
// Exclude null and undefined from the Maybe Type
export type Maybe<T extends {}> = T | null | undefined;
// Compare and Return Values with Extends and the Ternary Operator
type YouSayGoodbyeAndISayHello<T> = T extends "hello" ? "goodbye" : "hello";
// Infer Elements Inside a Conditional with Infer
type GetDataValue<T> = T extends { data: infer TData }
? TData
: never;
type tests = [
Expect<Equal<GetDataValue<{ data: "hello" }>, "hello">>,
Expect<Equal<GetDataValue<{ data: { name: "hello" } }>, { name: "hello" }>>,
Expect<
Equal<
GetDataValue<{ data: { name: "hello"; age: 20 } }>,
{ name: "hello"; age: 20 }
>
>,
// Expect that if you pass in string, it
// should return never
Expect<Equal<GetDataValue<string>, never>>,
];
// Use infer with Generics to Extract Types from Arguments
interface MyComplexInterface<Event, Context, Name, Point> {
getEvent: () => Event;
getContext: () => Context;
getName: () => Name;
getPoint: () => Point;
}
type Example = MyComplexInterface<
"click",
"window",
"my-event",
{ x: 12; y: 14 }
>;
type GetPoint<T> = T extends MyComplexInterface<any, any, any, any>
? any
: never;
type tests = [Expect<Equal<GetPoint<Example>, { x: 12; y: 14 }>>];
// Pattern Matching on Template Literals with Infer
type Names = [
"Matt Pocock",
"Jimi Hendrix",
"Eric Clapton",
"John Mayer",
"BB King",
];
type GetSurname<T> = T extends `${infer First} ${infer Last}` ? Last : never
type tests = [
Expect<Equal<GetSurname<Names[0]>, "Pocock">>,
Expect<Equal<GetSurname<Names[1]>, "Hendrix">>,
Expect<Equal<GetSurname<Names[2]>, "Clapton">>,
Expect<Equal<GetSurname<Names[3]>, "Mayer">>,
Expect<Equal<GetSurname<Names[4]>, "King">>,
];
// Optionally Infer the Return Type of a Function
const getServerSideProps = async () => {
const data = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const json: { title: string } = await data.json();
return {
props: {
json,
},
};
};
type InferPropsFromServerSideFunction<T> = T extends () => Promise<{
props: infer P
}>
? P
: never
;
type tests = [
Expect<
Equal<
InferPropsFromServerSideFunction<typeof getServerSideProps>,
{ json: { title: string } }
>
>
];
// Extract the Result From Several Possible Function Shapes
const parser1 = {
parse: () => 1,
};
const parser2 = () => "123";
const parser3 = {
extract: () => true,
};
type GetParserResult<T> = T extends
| {
parse: () => infer TResult
}
| {
extract: () => infer TResult
}
| (() => infer TResult)
? TResult
: never
type tests = [
Expect<Equal<GetParserResult<typeof parser1>, number>>,
Expect<Equal<GetParserResult<typeof parser2>, string>>,
Expect<Equal<GetParserResult<typeof parser3>, boolean>>,
];
// Use Mapped Types to Create an Object from a Union
type Route = "/" | "/about" | "/admin" | "/admin/users";
type RoutesObject = {
[R in Route]: R
}
type tests = [
Expect<
Equal<
RoutesObject,
{
"/": "/";
"/about": "/about";
"/admin": "/admin";
"/admin/users": "/admin/users";
}
>
>,
];
// Mapped Types with Objects
interface Attributes {
firstName: string;
lastName: string;
age: number;
}
type AttributeGetters = {
[K in keyof Attributes]: () => Attributes[K]
}
type tests = [
Expect<
Equal<
AttributeGetters,
{
firstName: () => string;
lastName: () => string;
age: () => number;
}
>
>,
];
// Transforming Object Keys in Mapped Types
interface Attributes {
firstName: string;
lastName: string;
age: number;
}
type AttributeGetters = {
[K in keyof Attributes as `get${Capitalize<K>}`]: () => Attributes[K]
}
type tests = [
Expect<
Equal<
AttributeGetters,
{
getFirstName: () => string;
getLastName: () => string;
getAge: () => number;
}
>
>
];
// Conditionally Extract Properties from Object
interface Example {
name: string;
age: number;
id: string;
organisationId: string;
groupId: string;
}
type SearchForId = `${string}${"id" | "Id"}${string}`
type OnlyIdKeys<T> = {
[K in keyof T as K extends SearchForId ? K : never]: T[K]
}
type tests = [
Expect<
Equal<
OnlyIdKeys<Example>,
{
id: string;
organisationId: string;
groupId: string;
}
>
>,
Expect<Equal<OnlyIdKeys<{}>, {}>>
];
// Map a Discriminated Union to an Object
type Route =
| {
route: "/";
search: {
page: string;
perPage: string;
};
}
| { route: "/about"; search: {} }
| { route: "/admin"; search: {} }
| { route: "/admin/users"; search: {} };
type RoutesObject = {
[R in Route as R["route"]]: R["search"]
}
type tests = [
Expect<
Equal<
RoutesObject,
{
"/": {
page: string;
perPage: string;
};
"/about": {};
"/admin": {};
"/admin/users": {};
}
>
>,
];
// Map an Object to a Union of Tuples
interface Values {
email: string;
firstName: string;
lastName: string;
}
type ValuesAsUnionOfTuples = {
[K in keyof Values]: [K, Values[K]]
}[keyof Values]
type tests = [
Expect<
Equal<
ValuesAsUnionOfTuples,
["email", string] | ["firstName", string] | ["lastName", string]
>
>
];
// Transform an Object into a Union of Template Literals
interface FruitMap {
apple: "red";
banana: "yellow";
orange: "orange";
}
type TransformedFruit = {
[K in keyof FruitMap]: `${K}:${FruitMap[K]}`
}[keyof FruitMap]
type tests = [
Expect<
Equal<TransformedFruit, "apple:red" | "banana:yellow" | "orange:orange">
>,
];
// Transform a Discriminated Union into a Union
type Fruit =
| {
name: "apple";
color: "red";
}
| {
name: "banana";
color: "yellow";
}
| {
name: "orange";
color: "orange";
};
type TransformedFruit = {
[F in Fruit as F["name"]]: `${F["name"]}:${F["color"]}`
}[Fruit["name"]]
type tests = [
Expect<
Equal<TransformedFruit, "apple:red" | "banana:yellow" | "orange:orange">
>,
];
// Transform an Object into a Discriminated Union
interface Attributes {
id: string;
email: string;
username: string;
}
/**
* How do we create a type helper that represents a union
* of all possible combinations of Attributes?
*/
type MutuallyExclusive<T> = {
[K in keyof T]: Record<K, T[K]>;
}[keyof T];
type ExclusiveAttributes = MutuallyExclusive<Attributes>;
type tests = [
Expect<
Equal<
ExclusiveAttributes,
| {
id: string;
}
| {
email: string;
}
| {
username: string;
}
>
>,
];
// Transform a Discriminated Union with Unique Values to an Object
type Route =
| {
route: "/";
search: {
page: string;
perPage: string;
};
}
| { route: "/about" }
| { route: "/admin" }
| { route: "/admin/users" };
type RoutesObject = {
[R in Route as R["route"]]: R extends { search: infer S } ? S : never;
};
type tests = [
Expect<
Equal<
RoutesObject,
{
"/": {
page: string;
perPage: string;
};
"/about": never;
"/admin": never;
"/admin/users": never;
}
>
>,
];
// Construct a Deep Partial of an Object
type DeepPartial<T> = T extends Array<infer U>
? Array<DeepPartial<U>>
: { [K in keyof T]?: DeepPartial<T[K]> };
type MyType = {
a: string;
b: number;
c: {
d: string;
e: {
f: string;
g: {
h: string;
i: string;
}[];
};
};
};
type Result = DeepPartial<MyType>;
type tests = [
Expect<
Equal<
Result,
{
a?: string;
b?: number;
c?: {
d?: string;
e?: {
f?: string;
g?: {
h?: string;
i?: string;
}[];
};
};
}
>
>
];
// Generics
// Typing Functions with Generics
const returnWhatIPassIn = <T>(t: T) => {
return t;
};
const one = returnWhatIPassIn(1);
const matt = returnWhatIPassIn("matt");
type tests = [Expect<Equal<typeof one, 1>>, Expect<Equal<typeof matt, "matt">>];
// Restricting Generic Argument Types
export const returnWhatIPassIn = <T extends string>(t: T) => t;
it("Should ONLY allow strings to be passed in", () => {
const a = returnWhatIPassIn("a");
type test1 = Expect<Equal<typeof a, "a">>;
// @ts-expect-error
returnWhatIPassIn(1);
// @ts-expect-error
returnWhatIPassIn(true);
// @ts-expect-error
returnWhatIPassIn({
foo: "bar",
});
});
// Typing Independent Parameters
const returnBothOfWhatIPassIn = <T, K>(a: T, b: K) => {
return {
a,
b,
};
};
it("Should return an object of the arguments you pass", () => {
const result = returnBothOfWhatIPassIn("a", 1);
expect(result).toEqual({
a: "a",
b: 1,
});
type test1 = Expect<
Equal<
typeof result,
{
a: string;
b: number;
}
>
>;
});
// Approaches for Typing Object Parameters
const returnBothOfWhatIPassIn = <T, K>(params: { a: T; b: K }) => {
return {
first: params.a,
second: params.b,
};
};
it("Should return an object where a -> first and b -> second", () => {
const result = returnBothOfWhatIPassIn({
a: "a",
b: 1,
});
expect(result).toEqual({
first: "a",
second: 1,
});
type test1 = Expect<
Equal<
typeof result,
{
first: string;
second: number;
}
>
>;
});
// Generics in Classes
export class Component<T> {
private props: T;
constructor(props: T) {
this.props = props;
}
getProps = () => this.props;
}
it("Should create an object containing props", () => {
const component = new Component({ a: 1, b: 2, c: 3 });
const result = component.getProps();
expect(result).toEqual({ a: 1, b: 2, c: 3 });
type tests = [
Expect<Equal<typeof result, { a: number; b: number; c: number }>>,
];
});
// Generic Mapper Function
export const concatenateFirstNameAndLastName = <
TUser extends { firstName: string; lastName: string },
>(
user: TUser,
) => {
return {
...user,
fullName: `${user.firstName} ${user.lastName}`,
};
};
it("Should add fullName to an object which only contains firstName and lastName", () => {
const users = [
{
firstName: "Matt",
lastName: "Pocock",
},
];
const newUsers = users.map(concatenateFirstNameAndLastName);
expect(newUsers).toEqual([
{
firstName: "Matt",
lastName: "Pocock",
fullName: "Matt Pocock",
},
]);
type tests = [
Expect<
Equal<
typeof newUsers,
Array<{ firstName: string; lastName: string } & { fullName: string }>
>
>,
];
});
it("Should retain other properties passed in", () => {
const users = [
{
id: 1,
firstName: "Matt",
lastName: "Pocock",
},
];
const newUsers = users.map(concatenateFirstNameAndLastName);
expect(newUsers).toEqual([
{
id: 1,
firstName: "Matt",
lastName: "Pocock",
fullName: "Matt Pocock",
},
]);
type tests = [
Expect<
Equal<
typeof newUsers,
Array<
{ id: number; firstName: string; lastName: string } & {
fullName: string;
}
>
>
>,
];
});
it("Should fail when the object passed in does not contain firstName", () => {
const users = [
{
firstName: "Matt",
},
];
const newUsers = users.map(
// @ts-expect-error
concatenateFirstNameAndLastName,
);
});
// Pass Type Arguments to a Reduce Function
const array = [
{
name: "John",
},
{
name: "Steve",
},
];
const obj = array.reduce<Record<string, { name: string }>>((accum, item) => {
accum[item.name] = item;
return accum;
}, {});
it("Should resolve to an object where name is the key", () => {
expect(obj).toEqual({
John: {
name: "John",
},
Steve: {
name: "Steve",
},
});
type tests = [Expect<Equal<typeof obj, Record<string, { name: string }>>>];
});
// Avoid any Types with Generics
const fetchData = async <TData>(url: string) => {
const data: TData = await fetch(url).then((response) => response.json());
return data;
};
it("Should fetch data from an API", async () => {
const data = await fetchData<{ name: string }>(
"https://swapi.py4e.com/api/people/1",
);
expect(data.name).toEqual("Luke Skywalker");
type tests = [Expect<Equal<typeof data, { name: string }>>];
});
// Generics at Different Levels
export const getHomePageFeatureFlags = <HomePageFlags>(
config: {
rawConfig: {
featureFlags: {
homePage: HomePageFlags;
};
};
},
override: (flags: HomePageFlags) => HomePageFlags
) => {
return override(config.rawConfig.featureFlags.homePage);
};
describe("getHomePageFeatureFlags", () => {
const EXAMPLE_CONFIG = {
apiEndpoint: "https://api.example.com",
apiVersion: "v1",
apiKey: "1234567890",
rawConfig: {
featureFlags: {
homePage: {
showBanner: true,
showLogOut: false,
},
loginPage: {
showCaptcha: true,
showConfirmPassword: false,
},
},
},
};
it("Should return the homePage flag object", () => {
const flags = getHomePageFeatureFlags(
EXAMPLE_CONFIG,
(defaultFlags) => defaultFlags
);
expect(flags).toEqual({
showBanner: true,
showLogOut: false,
});
type tests = [
Expect<Equal<typeof flags, { showBanner: boolean; showLogOut: boolean }>>
];
});
it("Should allow you to modify the result", () => {
const flags = getHomePageFeatureFlags(EXAMPLE_CONFIG, (defaultFlags) => ({
...defaultFlags,
showBanner: false,
}));
expect(flags).toEqual({
showBanner: false,
showLogOut: false,
});
type tests = [
Expect<Equal<typeof flags, { showBanner: boolean; showLogOut: boolean }>>
];
});
});
// Two Approaches for Typing Object Keys
const typedObjectKeys = <TKey extends string>(obj: Record<TKey, any>) => {
return Object.keys(obj) as Array<TKey>;
};
it("Should return the keys of the object", () => {
const result1 = typedObjectKeys({
a: 1,
b: 2,
});
expect(result1).toEqual(["a", "b"]);
type test = Expect<Equal<typeof result1, Array<"a" | "b">>>;
});
// Constrain a Type Argument to a Function
import { expect, it } from "vitest";
import { Equal, Expect } from "../helpers/type-utils";
const makeSafe =
<TFunc extends (...args: any[]) => any>(func: TFunc) =>
(
...args: Parameters<TFunc>
):
| {
type: "success";
result: ReturnType<TFunc>;
}
| {
type: "failure";
error: Error;
} => {
try {
const result = func(...args);
return {
type: "success",
result,
};
} catch (e) {
return {
type: "failure",
error: e as Error,
};
}
};
(func: unknown) =>
(
...args: unknown
):
| {
type: "success";
result: unknown;
}
| {
type: "failure";
error: Error;
} => {
try {
const result = func(...args);
return {
type: "success",
result,
};
} catch (e) {
return {
type: "failure",
error: e as Error,
};
}
};
it("Should return the result with a { type: 'success' } on a successful call", () => {
const func = makeSafe(() => 1);
const result = func();
expect(result).toEqual({
type: "success",
result: 1,
});
type tests = [
Expect<
Equal<
typeof result,
| {
type: "success";
result: number;
}
| {
type: "failure";
error: Error;
}
>
>,
];
});
it("Should return the error on a thrown call", () => {
const func = makeSafe(() => {
if (1 > 2) {
return "123";
}
throw new Error("Oh dear");
});
const result = func();
expect(result).toEqual({
type: "failure",
error: new Error("Oh dear"),
});
type tests = [
Expect<
Equal<
typeof result,
| {
type: "success";
result: string;
}
| {
type: "failure";
error: Error;
}
>
>,
];
});
it("Should properly match the function's arguments", () => {
const func = makeSafe((a: number, b: string) => {
return `${a} ${b}`;
});
// @ts-expect-error
func();
// @ts-expect-error
func(1, 1);
func(1, "1");
});
// Inferring Literal Types from any Basic Type
export const inferItemLiteral = <T extends string | number>(t: T) => {
return {
output: t,
}
}
const result1 = inferItemLiteral("a");
const result2 = inferItemLiteral(123);
type tests = [
Expect<Equal<typeof result1, { output: "a" }>>,
Expect<Equal<typeof result2, { output: 123 }>>
];
// @ts-expect-error
const error1 = inferItemLiteral({
a: 1,
});
// @ts-expect-error
const error2 = inferItemLiteral([1, 2]);
// Constrain to the Array Member, Not the Array
const makeStatus = <TStatus extends string>(statuses: TStatus[]) => {
return statuses;
};
const statuses = makeStatus(["INFO", "DEBUG", "ERROR", "WARNING"]);
type tests = [
Expect<Equal<typeof statuses, Array<"INFO" | "DEBUG" | "ERROR" | "WARNING">>>,
];
// Generics in a Class Names Creator
const createClassNamesFactory =
<TClasses extends Record<string, string>>(classes: TClasses) =>
(type: keyof TClasses, ...otherClasses: string[]) => {
const classList = [classes[type], ...otherClasses];
return classList.join(" ");
};
const getBg = createClassNamesFactory({
primary: "bg-blue-500",
secondary: "bg-gray-500",
});
it("Should let you create classes from a className factory", () => {
expect(getBg("primary")).toEqual("bg-blue-500");
expect(getBg("secondary")).toEqual("bg-gray-500");
});
it("Should let you pass additional classes which get appended", () => {
expect(getBg("primary", "text-white", "rounded", "p-4")).toEqual(
"bg-blue-500 text-white rounded p-4"
);
});
it("Should return a type of string", () => {
const result = getBg("primary");
type test = Expect<Equal<typeof result, string>>;
});
it("Should not let you pass invalid variants", () => {
// @ts-expect-error
getBg("123123");
});
it("Should not let you pass an invalid object to createClassNamesFactory", () => {
// @ts-expect-error
createClassNamesFactory([]);
// @ts-expect-error
createClassNamesFactory(123);
createClassNamesFactory({
// @ts-expect-error
a: 1,
});
});
// Generics with Conditional Types
function youSayGoodbyeISayHello<TGreeting extends "hello" | "goodbye">(
greeting: TGreeting,
): TGreeting extends "hello" ? "goodbye" : "hello" {
return (greeting === "goodbye" ? "hello" : "goodbye"); as any
}
it("Should return goodbye when hello is passed in", () => {
const result = youSayGoodbyeISayHello("hello");
type test = [Expect<Equal<typeof result, "goodbye">>];
expect(result).toEqual("goodbye");
});
it("Should return hello when goodbye is passed in", () => {
const result = youSayGoodbyeISayHello("goodbye");
type test = [Expect<Equal<typeof result, "hello">>];
expect(result).toEqual("hello");
});
// Fixing Errors in Generic Functions
type Person = {
name: string;
age: number;
birthdate: Date;
};
export function remapPerson<Key extends keyof Person>(
key: Key,
value: Person[Key],
): Person[Key] {
if (key === "birthdate") {
return new Date() as Person[Key];
}
return value;
}
const date = remapPerson("birthdate", new Date());
const num = remapPerson("age", 42);
const name = remapPerson("name", "John Doe");
type tests = [
Expect<Equal<typeof date, Date>>,
Expect<Equal<typeof num, number>>,
Expect<Equal<typeof name, string>>,
];
// Infer Types from Type Arguments
export class Component<TProps> {
private props: TProps;
constructor(props: TProps) {
this.props = props;
}
getProps = () => this.props;
}
const cloneComponent = <TProps>(component: Component<TProps>): Component<TProps> => {
return new Component(component.getProps());
};
it("Should clone the props from a passed-in Component", () => {
const component = new Component({ a: 1, b: 2, c: 3 });
const clonedComponent = cloneComponent(component);
const result = clonedComponent.getProps();
expect(result).toEqual({ a: 1, b: 2, c: 3 });
type tests = [
Expect<Equal<typeof result, { a: number; b: number; c: number }>>
];
});
// Strongly Type a Reduce Function
const array = [
{
name: "John",
},
{
name: "Steve",
},
];
const obj = array.reduce<Record<string, { name: string }>>((accum, item) => {
accum[item.name] = item;
return accum;
}, {});
it("Should resolve to an object where name is the key", () => {
expect(obj).toEqual({
John: {
name: "John",
},
Steve: {
name: "Steve",
},
});
type tests = [Expect<Equal<typeof obj, Record<string, { name: string }>>>];
});
// Avoid any Types with Generics
const fetchData = async <TData>(url: string) => {
let data: TData = await fetch(url).then((response) => response.json());
return data;
};
it("Should fetch data from an API", async () => {
const data = await fetchData<{ name: string }>(
"https://swapi.py4e.com/api/people/1",
);
expect(data.name).toEqual("Luke Skywalker");
type tests = [Expect<Equal<typeof data, { name: string }>>];
});
// Represent Generics at the Lowest Level
export const getHomePageFeatureFlags = <HomePageFlags>(
config: {
rawConfig: {
featureFlags: {
homePage: HomePageFlags;
};
};
},
override: (flags: HomePageFlags) => HomePageFlags
) => {
return override(config.rawConfig.featureFlags.homePage);
};
describe("getHomePageFeatureFlags", () => {
const EXAMPLE_CONFIG = {
apiEndpoint: "https://api.example.com",
apiVersion: "v1",
apiKey: "1234567890",
rawConfig: {
featureFlags: {
homePage: {
showBanner: true,
showLogOut: false,
},
loginPage: {
showCaptcha: true,
showConfirmPassword: false,
},
},
},
};
it("Should return the homePage flag object", () => {
const flags = getHomePageFeatureFlags(
EXAMPLE_CONFIG,
(defaultFlags) => defaultFlags
);
expect(flags).toEqual({
showBanner: true,
showLogOut: false,
});
type tests = [
Expect<Equal<typeof flags, { showBanner: boolean; showLogOut: boolean }>>
];
});
it("Should allow you to modify the result", () => {
const flags = getHomePageFeatureFlags(EXAMPLE_CONFIG, (defaultFlags) => ({
...defaultFlags,
showBanner: false,
}));
expect(flags).toEqual({
showBanner: false,
showLogOut: false,
});
type tests = [
Expect<Equal<typeof flags, { showBanner: boolean; showLogOut: boolean }>>
];
});
});
// Typed Object Keys
// Two Approaches for Typing Object Keys
const typedObjectKeys = <TKey extends string>(obj: Record<TKey, any>) => {
return Object.keys(obj) as Array<TKey>;
};
it("Should return the keys of the object", () => {
const result1 = typedObjectKeys({
a: 1,
b: 2,
});
expect(result1).toEqual(["a", "b"]);
type test = Expect<Equal<typeof result1, Array<"a" | "b">>>;
});
// Fixing Errors in Generic Functions
// Fixing the "Not Assignable" Error
type Person = {
name: string;
age: number;
birthdate: Date;
};
export function remapPerson<Key extends keyof Person>(
key: Key,
value: Person[Key],
): Person[Key] {
if (key === "birthdate") {
return new Date() as Person[Key];
}
return value;
}
const date = remapPerson("birthdate", new Date());
const num = remapPerson("age", 42);
const name = remapPerson("name", "John Doe");
type tests = [
Expect<Equal<typeof date, Date>>,
Expect<Equal<typeof num, number>>,
Expect<Equal<typeof name, string>>,
];
// Generic Function Currying
export const curryFunction =
<T>(t: T) =>
<U>(u: U) =>
<V>(v: V) => {
return {
t,
u,
v,
};
};
it("Should return an object which matches the types of each input", () => {
const result = curryFunction(1)(2)(3);
expect(result).toEqual({
t: 1,
u: 2,
v: 3,
});
type test = [
Expect<Equal<typeof result, { t: number; u: number; v: number }>>,
];
});
// Generic Interfaces with Functions
export interface Cache<T> {
get: (key: string) => T | undefined;
set: (key: string, value: T) => void;
// You can fix this by only changing the line below!
clone: <U>(transform: (elem: T) => U) => Cache<U>;
}
const createCache = <T>(initialCache?: Record<string, T>): Cache<T> => {
const cache: Record<string, T> = initialCache || {};
return {
get: (key) => cache[key],
set: (key, value) => {
cache[key] = value;
},
clone: (transform) => {
const newCache: Record<string, any> = {};
for (const key in cache) {
newCache[key] = transform(cache[key]);
}
return createCache(newCache);
},
};
};
it("Should let you get and set to/from the cache", () => {
const cache = createCache<number>();
cache.set("a", 1);
cache.set("b", 2);
expect(cache.get("a")).toEqual(1);
expect(cache.get("b")).toEqual(2);
});
it("Should let you clone the cache using a transform function", () => {
const numberCache = createCache<number>();
numberCache.set("a", 1);
numberCache.set("b", 2);
const stringCache = numberCache.clone((elem) => {
return String(elem);
});
const a = stringCache.get("a");
expect(a).toEqual("1");
type tests = [Expect<Equal<typeof a, string | undefined>>];
});
// Refactoring Functions with Unnecessary Type Arguments
const returnBothOfWhatIPassIn = <
TParams extends {
a: unknown;
b: unknown;
},
>(
params: TParams,
): [TParams["a"], TParams["b"]] => {
return [params.a, params.b];
};
it("Should return a tuple of the properties a and b", () => {
const result = returnBothOfWhatIPassIn({
a: "a",
b: 1,
});
expect(result).toEqual(["a", 1]);
type test1 = Expect<Equal<typeof result, [string, number]>>;
});
// Improving Type Inference with Additional Generics
const getValue = <TObj, TKey extends keyof TObj>(obj: TObj, key: TKey) => {
return obj[key];
};
const obj = {
a: 1,
b: "some-string",
c: true,
};
const numberResult = getValue(obj, "a");
const stringResult = getValue(obj, "b");
const booleanResult = getValue(obj, "c");
type tests = [
Expect<Equal<typeof numberResult, number>>,
Expect<Equal<typeof stringResult, string>>,
Expect<Equal<typeof booleanResult, boolean>>,
];
// Create a Factory Function to Apply Type Arguments to All Child Functions
const makeUseStyled = <TTheme = {}>() => {
const useStyled = (func: (theme: TTheme) => CSSProperties) => {
return {} as CSSProperties
}
return useStyled
}
interface MyTheme {
color: {
primary: string;
};
fontSize: {
small: string;
};
}
const useStyled = makeUseStyled<MyTheme>()
const buttonStyle = useStyled((theme) => ({
color: theme.color.primary,
fontSize: theme.fontSize.small,
}))
const divStyle = useStyled((theme) => ({
backgroundColor: theme.color.primary,
}))
//
// Pass Type Arguments to a Function
export const createSet = <T>() => {
return new Set<T>();
};
const stringSet = createSet<string>();
const numberSet = createSet<number>();
const unknownSet = createSet();
type tests = [
Expect<Equal<typeof stringSet, Set<string>>>,
Expect<Equal<typeof numberSet, Set<number>>>,
Expect<Equal<typeof unknownSet, Set<unknown>>>,
];
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment