Utility Types for React
Recipe
Use TypeScript's built-in utility types to transform and compose React prop types. Derive new types from existing ones instead of duplicating definitions.
Working Example
// Base type
type User = {
id: string;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
createdAt: Date;
};
// Partial - all fields become optional (great for update forms)
type UpdateUserPayload = Partial<User>;
// Pick - select specific fields
type UserPreview = Pick<User, "id" | "name">;
// Omit - exclude specific fields
type CreateUserPayload = Omit<User, "id" | "createdAt">;
// Record - typed key-value map
type RolePermissions = Record<User["role"], string[]>;
const permissions: RolePermissions = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
};// Practical component using derived types
type UserFormProps = {
initialData?: Partial<User>;
onSubmit: (data: CreateUserPayload) => void;
};
function UserForm({ initialData, onSubmit }: UserFormProps) {
const [form, setForm] = useState<CreateUserPayload>({
name: initialData?.name ?? "",
email: initialData?.email ?? "",
role: initialData?.role ?? "viewer",
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
onSubmit(form);
}}
>
<input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
<input
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
/>
<button type="submit">Save</button>
</form>
);
}Deep Dive
How It Works
Partial<T>makes every property inToptional. Useful for update payloads where you only change some fields.Required<T>is the inverse ofPartial-- makes every property required. Useful for normalizing config objects with defaults.Pick<T, K>creates a type with only the specified keys fromT. Use it to create focused sub-types.Omit<T, K>creates a type with all keys except the specified ones. Common for create payloads where the server generatesid.Record<K, V>creates an object type with keys of typeKand values of typeV. Perfect for lookup maps.Extract<T, U>pulls members from a union that matchU.Extract<User["role"], "admin" | "editor">yields"admin" | "editor".Exclude<T, U>removes members from a union that matchU. The inverse ofExtract.
Variations
Extracting component prop types:
// Get props from an existing component
type ButtonProps = React.ComponentProps<typeof Button>;
// Get props of an HTML element
type DivProps = React.ComponentPropsWithoutRef<"div">;
// Extend HTML element props
type CardProps = React.ComponentPropsWithoutRef<"div"> & {
title: string;
elevated?: boolean;
};Readonly props:
type ImmutableUser = Readonly<User>;
// All fields are now readonly - prevents accidental mutation
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};Mapped types for form state:
type FormErrors<T> = Partial<Record<keyof T, string>>;
type UserFormErrors = FormErrors<CreateUserPayload>;
// { name?: string; email?: string; role?: string }NonNullable for stripping null/undefined:
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // UserTypeScript Notes
- Utility types compose:
Partial<Pick<User, "name" | "email">>gives you{ name?: string; email?: string }. React.ComponentProps<typeof MyComponent>extracts the props type from any component, even third-party ones where the type is not exported.Parameters<T>andReturnType<T>work on function types.ReturnType<typeof useAuth>gives you the return type of a custom hook.
Gotchas
Omitdoes not error on keys that do not exist in the original type.Omit<User, "nonExistent">silently returns the fullUsertype.Partialmakes everything optional, including fields that should remain required. UsePartial<Pick<T, K>> & Omit<T, K>to make only some fields optional.Record<string, T>allows any string key. This can hide typos. Prefer union literal keys when the set of keys is known.- Deeply nested objects are not affected by
PartialorReadonly. These only operate on the top-level properties. You need custom recursive types for deep transformations.
Alternatives
| Approach | Pros | Cons |
|---|---|---|
Partial<T> for updates | Derived from source type, stays in sync | Makes all fields optional, not just some |
| Manual sub-types | Full control over each type | Duplicates definitions, drifts from source |
Pick / Omit composition | Precise field selection | Deeply nested compositions are hard to read |
Zod .pick() / .omit() | Runtime validation + type inference | Requires Zod dependency |
satisfies operator | Validates shape without widening | Does not create a reusable type |
FAQs
What does Partial<T> do and when should I use it?
- It makes every property in
Toptional. - Use it for update payloads where you only change some fields, e.g.,
Partial<User>for a PATCH request body.
What is the difference between Pick and Omit?
Pick<T, K>creates a type with only the specified keys fromT.Omit<T, K>creates a type with all keys except the specified ones.- Use
Pickwhen you want a small subset; useOmitwhen you want most fields minus a few.
How do I extract the props type from an existing component?
type ButtonProps = React.ComponentProps<typeof Button>;
type DivProps = React.ComponentPropsWithoutRef<"div">;- Works on both custom components and HTML elements.
What does Record<K, V> do?
- It creates an object type with keys of type
Kand values of typeV. - Example:
Record<User["role"], string[]>maps each role to an array of permission strings.
What is the difference between Extract and Exclude?
Extract<T, U>keeps union members that matchU.Exclude<T, U>removes union members that matchU.- Example:
Extract<"admin" | "editor" | "viewer", "admin" | "editor">yields"admin" | "editor".
Can I compose utility types together?
- Yes. They compose freely:
Partial<Pick<User, "name" | "email">>gives{ name?: string; email?: string }. - Composition keeps types derived from a single source of truth.
Gotcha: Does Omit error if I specify a key that does not exist on the type?
- No.
Omit<User, "nonExistent">silently returns the fullUsertype. - TypeScript does not warn you, so double-check your key names.
Gotcha: Does Partial work on deeply nested objects?
- No.
Partialonly affects top-level properties. - For deep optionality, you need a custom recursive type like
DeepPartial<T>.
How do I make only some fields optional while keeping others required?
type UpdateUser = Partial<Pick<User, "name" | "email">> & Omit<User, "name" | "email">;- Combine
Partial<Pick<>>for the optional fields withOmit<>for the rest.
What is NonNullable<T> used for?
- It strips
nullandundefinedfrom a type. NonNullable<User | null | undefined>givesUser.
How do I get the return type of a custom hook?
type AuthData = ReturnType<typeof useAuth>;ReturnType<T>works on any function type, including hooks and utility functions.
What is a mapped type and how does FormErrors use it?
type FormErrors<T> = Partial<Record<keyof T, string>>;Record<keyof T, string>maps every key ofTto astringvalue.- Wrapping in
Partialmakes each error message optional.