TypeScript Advanced Types for Next.js: Examples and Best Practices In 2023
Deep Partial
Sometimes, you may want to make only some properties of an object optional, without affecting the type of the object itself. You can create a DeepPartial utility that makes all properties of an object and nested objects optional. This can be useful when working with complex data models.
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}
How to Use Deep Partial
Here’s an example of how you could use DeepPartial in a Next.js project:
interface User {
name: string
address: {
street: string
city: string
}
}
type DeepPartialUser = DeepPartial<User>
// The DeepPartialUser type is equivalent to:
interface PartialUser {
name?: string
address?: {
street?: string
city?: string
}
}
const user: DeepPartialUser = {
address: {
street: "123 Main St.",
},
}
Optional Props
You can create an OptionalProps utility that makes some properties of a component’s props optional, while keeping the rest of the properties required. This can be useful when you have a large props interface with some optional and some required properties.
type OptionalProps<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
How to Use Optional Props
Here’s an example of how you could use OptionalProps in a Next.js project:
interface ButtonProps {
text: string
onClick: () => void
disabled: boolean
size: "small" | "medium" | "large"
}
type OptionalButtonProps = OptionalProps<ButtonProps, "size" | "disabled">
// The OptionalButtonProps type is equivalent to:
interface ButtonPropsOptional {
text: string
onClick: () => void
size?: "small" | "medium" | "large"
disabled?: boolean
}
function Button(props: OptionalButtonProps) {
// ...
}
;<Button text="Click me" onClick={() => console.log("Button clicked")} />
Merge Types
Sometimes, you may want to merge two or more types together to create a new type. You can create a MergeTypes utility that takes multiple types as arguments and returns a new type that merges all the properties of the input types.
type MergeTypes<T extends object[]> = T[number];
How to Use Merge Types
Here’s an example of how you could use MergeTypes in a Next.js project:
interface User {
name: string
email: string
}
interface Address {
street: string
city: string
}
type UserInfo = MergeTypes<[User, Address]>
// The UserInfo type is equivalent to:
interface UserInfoMerged {
name: string
email: string
street: string
city: string
}
const user: UserInfo = {
name: "John Doe",
email: "john.doe@example.com",
street: "123 Main St.",
city: "New York",
}
Promise Type
You can create a PromiseType utility that extracts the resolved type of a Promise. This can be useful when working with asynchronous code that returns promises.
type PromiseType<T extends Promise<any>> = T extends Promise<infer U>
? U
: never
How to Use Promise Type
Here’s an example of how you could use PromiseType in a Next.js project:
async function fetchUserData(): Promise<User> {
// fetch user data from API
}
type UserPromise = PromiseType<typeof fetchUserData>
// The UserPromise type is equivalent to:
interface User {
name: string
email: string
// ...
}
async function getUser(): Promise<UserPromise> {
const user = await fetchUserData()
return user
}
Readonly Props
You can create a ReadonlyProps utility that makes all properties of a component’s props readonly. This can be useful when you want to prevent the props from being modified by the component.
type ReadonlyProps<T> = { readonly [P in keyof T]: T[P] }
How to Use Readonly Props
Here’s an example of how you could use ReadonlyProps in a Next.js project:
interface ButtonProps {
readonly text: string
readonly onClick: () => void
}
function Button(props: ReadonlyProps<ButtonProps>) {
// ...
}
;<Button text="Click me" onClick={() => console.log("Button clicked")} />
// This would be a compile-time error:
// <Button text="Click me" onClick={() => console.log("Button clicked")} />
// ^-- error: Cannot assign to 'text' because it is a read-only property.