TypeScript Snippets
General TypeScript
Basics
const numbers: number[] = [1, 2, 3, 4, 5];
const sum: number = numbers.reduce<number>((acc, curr) => {
return acc + curr;
}, 0);
console.log(sum); // Output: 15
In this example, the type parameter for the reduce method is specified as number, which indicates that the reduce method should expect an array of numbers.
as const
- In TypeScript,
as constis a type assertion that tells the compiler to infer a "literal" type for an expression. - When applied to an object or an array, as const makes all the properties or elements read-only and immutable, so that they cannot be changed or reassigned. This can be useful for ensuring that certain values remain constant throughout the program.
// Object Example
const person = {
name: 'Alice',
age: 30,
address: {
city: 'London',
country: 'UK',
},
} as const;
person.name = 'Bob'; // Error: Cannot assign to 'name' because it is a read-only property.
person.address.city = 'Paris'; // Error: Cannot assign to 'city' because it is a read-only property.
// Array Example
const fruits = ['apple', 'banana', 'orange'] as const;
fruits[0] = 'pear'; // Error: Index signature in type 'readonly ["apple", "banana", "orange"]' only permits reading.
- with both the
personobject and thefruitsarray, we can ensure that neither of them can be changed or reassigned at runtime - this also means that we can't add additional properties to the object nor can we add or remove items from the fruits array
Routes as const Example
CM: I love this, super useful for routes
const BASE_URL = "https://www.example.com" as const;
const routes = {
HOME: `${BASE_URL}/`,
ABOUT: `${BASE_URL}/about`,
CONTACT: `${BASE_URL}/contact`,
PRODUCTS: `${BASE_URL}/products`,
LOGIN: `${BASE_URL}/login`,
} as const;
routes.ABOUT;
And now if you look at what happens in VS Code, I can hover over routes.ABOUT and see the full
route:

Interface as const Example
- Here's another example:
const UserType = {
Admin: "admin_user",
Basic: "basic_user",
} as const;
interface UserTypeResponse {
level: (typeof UserType)[keyof typeof UserType];
name: string;
position: string;
}
- The above code defines an object
UserTypewith two properties,AdminandBasic, which both have string values. - The
as constassertion ensures that the properties ofUserTypeare readonly and cannot be modified. - The interface
UserTypeResponseis also defined and, in this example, is what we'd receive back from a server, for example. The interface has three properties:level: This property is defined as a union type that maps the keys ofUserTypeto their corresponding values. This means thatlevelcan only have the values'admin_user'or'basic_user'(and NOT'admin'or'basic'')name: A string property that holds the name of the user.position: A string property that holds the position of the user.
- The
UserTypeResponseinterface is then used as the type for theuservariable, which is assigned an object that matches the interface. - The
uservariable is then logged to the console, which outputs the following:
{
level: 'admin_user',
name: 'John Doe',
position: 'Software Engineer'
}
Dynamically Creating Types
- I had a situation where I needed to create a large object that had two levels: a broader level that (for
this example) was the league type (e.g.
NFLorNBA) and then the team city (e.g.PHILADELPHIAorNEW YORK). - So the object would look something like this with simple JavaScript:
const NFL = "NFL";
const NBA = "NBA";
const PHILADELPHIA = "PHILADELPHIA";
const NEW_YORK = "NEW_YORK";
const teamObject = {
[NFL]: {
[`NFL-${PHILADELPHIA}`]: {
teams: ["Eagles"],
},
[`NBA-${NEW_YORK}`]: {
teams: ["Giants", "Jets"],
},
},
[NBA]: {
[`NBA-${PHILADELPHIA}`]: {
teams: ["Sixers"],
},
[`NBA-${NEW_YORK}`]: {
teams: ["Knicks", "Nets"],
},
},
};
export type TeamObject = {
teams: string[];
};
export type TEAM_REGIONS = "PHILADELPHIA" | "NEW_YORK";
type LeagueCodesNFL = `NFL-${TEAM_REGIONS}`;
type LeagueCodesNBA = `NBA-${TEAM_REGIONS}`;
export type LeagueCodes = LeagueCodesNFL | LeagueCodesNBA;
export type Leagues = {
NFL: Map<LeagueCodesNFL, TeamObject[]>;
NBA: Map<LeagueCodesNBA, TeamObject[]>;
};
export type LeaguesMap = Map<Leagues, TeamObject[]>;
Indexed Access
The syntax I've seen a lot looks like this CustomType['property] and is called indexed access.
We can't just do CustomType.property to set a particular type because you'd be accessing a value on
an object, not extracting a type. TypeScript types don't exist at runtime so you can't access properties
on them like objects.
So besides the syntactic reason for using MyType['someProperty'], another reason to use this instead of
going through the hassle of importing whatever type someProperty is that it gives you a single
source of truth. You now just need to import MyType. It reduces duplication as well.
The key takeaway is that it really isn't some TypeScript trick or gotcha - you are just using the type of some property from your parent type.
// Basic type definition
type User = {
id: number;
name: string;
email: string;
preferences: {
theme: "light" | "dark";
notifications: boolean;
};
};
// Indexed access examples
type UserId = User["id"]; // number
type UserEmail = User["email"]; // string
type UserTheme = User["preferences"]["theme"]; // 'light' | 'dark'
// Multiple properties
type UserContact = User["name" | "email"]; // string
// Using in function parameters
function updateUser(
userId: User["id"], // number
newEmail: User["email"], // string
theme: User["preferences"]["theme"] // 'light' | 'dark'
) {
// Function implementation...
}
// Alternative without indexed access (more verbose)
function updateUserVerbose(
userId: number, // Have to manually keep in sync
newEmail: string, // with User type definition
theme: "light" | "dark"
) {
// If User type changes, this breaks!
}
Pick
I don't use Pick a lot but here is a simple example and a cool way to create a new type without
having to type out all the properties:
type ProductCardProps = {
name: string;
price: number;
imageUrl: string | undefined | null;
isOnSale: boolean;
salePercentage: number | undefined | null;
category: string;
description: ReactNode;
inventoryCount: number;
};
type ProductPriceTagProps = Pick<
ProductCardProps,
"price" | "isOnSale" | "salePercentage"
>;
So now ProductPriceTagProps is a new type that only has the price, isOnSale, and salePercentage.
Typing a Reducer
- Here is an example for a reducer (React's
useReducer) where I wanted two main types,TOGGLE_CELLandTOGGLE_CONSTRAINT, but every time I did{ type: TOGGLE_CELL; ...}it would give me an error. I created separate types for each and then could build it out
export const TOGGLE_CELL = "TOGGLE_CELL";
export const TOGGLE_CONSTRAINT = "TOGGLE_CONSTRAINT";
// Use `typeof` to create types from the constants
type ToggleCellActionType = typeof TOGGLE_CELL;
type ToggleConstraintActionType = typeof TOGGLE_CONSTRAINT;
export type Action =
| { type: ToggleCellActionType; rowIndex: number; cellIndex: number }
| {
type: ToggleConstraintActionType;
rowIndex: number;
cellIndex: number;
direction: Direction;
};
React TypeScript
- Here is a basic functional component with TypeScript props
import React from "react";
interface Props {
name: string;
age: number;
}
const MyComponent = ({ name, age }: Props) => {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
);
};
export default MyComponent;
- Here is the same component but using the
FunctionComponenttype
import React, { FunctionComponent } from "react";
interface Props {
name: string;
age: number;
}
const MyComponent: FunctionComponent<Props> = ({ name, age }) => {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
);
};
export default MyComponent;
- Just looking around, I'm not sure there are real benefits to using the
FunctionComponenttype. See this article.
Zustand-TypeScript Snippets
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { PREFIX } from "@/constants/stores";
export type MetaDataState = {
useLoggedIn: boolean;
};
type MetaDataActions = {
getKey: (key: string) => unknown;
setKey: (key: string, value: unknown) => void;
};
export const useMetaDataStore = create<MetaDataState & MetaDataActions>()(
persist(
(set, get) => ({
useLoggedIn: false,
getKey: (key: string) => get()[key as keyof MetaDataState],
setKey: (key: string, value: unknown) => set({ [key]: value }),
}),
{
name: `${PREFIX}__METADATA__`,
}
)
);
export const getMetaDataKey = (key: string) =>
useMetaDataStore.getState().getKey(key);
export const setMetaDataKey = (key: string, value: unknown) =>
useMetaDataStore.getState().setKey(key, value);
-
I like the combination of the two types - I think it's a cool way to separate the state from the actions.
-
getKey: (key: string) => get()[key as keyof MetaDataState],this code was tricky because it was yelling at me so I had to add theas keyof MetaDataStateto get it to work. Here's the original error:Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'MetaDataState & MetaDataActions'. No index signature with a parameter of type 'string' was found on type 'MetaDataState & MetaDataActions'
-
This is also a really good basic implementation of Zustand with
createandpersistmiddleware.