---
title: Useful utilities
teaser: 'A strict type system doesn''t mean inflexible. We can bend it to meet our
  needs with the utilities that ship with TypeScript.

  '
tags: typescript,types,good code
author: Alejandro Dustet
published_on: 2021-02-01
---

The health of a type system relies heavily upon how malleable it is, how clearly
can it describe our domain's description, and how good it can uphold our
solution. Sometimes, we hear that a type system is too rigid, or it adds too
many constraints as an argument against it. In our experience with the
TypeScript type system, this is very far from the truth -- mainly thanks to a
great set of [utilities].

[utilities]: https://www.typescriptlang.org/docs/handbook/utility-types.html

## Utilities you say

First things first, what are these utilities? The purpose of most of them could
be **type transformation**. They are used to construct new type definitions or
to compose and modify the existing ones for reusability. Since taxonomies are in
our nature, we could group them into categories like:

- Add or remove constraints: [`Partial`], [`Readonly`], or [`Required`].
- Filter certain properties: [`NonNullable`].
- Compose sub-types from existing ones: [`Pick`] or [`Omit`].
- Infer types: [`Parameters`] or [`ReturnType`].

[`Partial`]: https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype
[`Readonly`]: https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype
[`Required`]: https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype
[`NonNullable`]: https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype
[`Pick`]: https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys
[`Omit`]: https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys
[`Parameters`]: https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype
[`ReturnType`]: https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype

These are in no form exclusive, and one can fulfill another's role with some
tinkering or **Type Gymnastics ©**.

### The most versatile `Record`

A `JSON object,` or a collection of attributes, or even a hashmap, could be
the broadest classification of any data we have in TypeScript. More often than
not, we will need to handle some arbitrary set of properties. Typing those could
be a nightmare. Sometimes we do, however, have some indication of the general
shape of an object. What do we want to store (a value) and how might we retrieve
it (a key). Having those under the type system can be a powerful ally. Consider
this example to render the `TabBarIcon` using [react-navigation]

[react-navigation]: https://reactnavigation.org/

``` typescript
// Parameters that the tab bar elements will receive
// this type declaration would be required
// by react navigation
type TabBarParamList = {
  Home: undefined
  Settings: undefined
};
// We gather all possible items on the TabBar
type TabBarItem = keyof TabBarParamList
// Posible icon names
type IconName = "home-icon" | "settings-icon"
// Here we declare that the collection of tab bar
// items will be composed of Tab Bar Items as keys
// and an icon as the value, enforcing that all Tab Bar
// Items are assigned an icon
type TabBarIconMapping = Record<TabBarItem, IconName>

const iconMapping: TabBarIconMapping = {
  ["Home"]: "home-icon",
  ["Settings"]: "settings-icon",
};

interface TabBarIconProps {
  routeName: TabBarItem
}
// This would be the component we provide to
// react navigation to render
const TabBarIcon: FunctionComponent<TabBarIconProps> =
({
  routeName,
}) => {
  return <Icon name={iconMapping[routeName]} />
};
```

We now have a representation of the problem that is complete. As we continue to
add items to our tab bar, the type system will take us through all the additions
we need to make. When we add a new element to the `TabBar` we add it to the
`TabBarParamList` and let TypeScript lead the way.

### Putting the Util in utilities: `Pick` and `Omit`

A pair that shines when we need to stretch our type definitions is `Pick` and
`Omit`. A personal favorite use-case is when we extract smaller
presentational components from one with a moderate amount of properties. Let's
consider a chart displaying element. In general, data that it's worth showing in
this format tends to be complicated. It is normal to break apart sections of the
chart to display portions of the data.

```typescript
type Trend = "Upward" | "Downward"
type Snapshot = {
  id: number,
  income: number,
  trend: Trend,
  // Other attributes used on
  // Income component
  source: string,
}

type IncomeProps = Pick<Snapshot, "income" | "trend">
// or with Omit if it is shorter, I find Omit harder
// to read unless the type is defined right here
type IncomePropsOmit = Omit<Snapshot, "id" | "source">

export const Income: FunctionComponent<IncomeProps> = (
  { value, trend }
) => {
  // Rendering the component
}
```

We can always create another interface or type. But in this case, if the data
changes significantly, we wouldn't get alerted by the type system to fix our
implementation.

One symptom to keep an eye out for using `Pick` and `Omit` is that we might end
up with scattered pieces of type clips. Overusing them might indicate that the
way data flows through our components is fighting the data model itself. So if
you catch yourself using these a lot, it might be a sign that our approach could
use a second look.

### Going meta: `Parameters` and `ReturnType`

When we have a value and need to know its type, we can use [typeof]. What
happens when we use it on a function? We always get back `Function`. When we
need information about the return type or the parameters it needs, we can now
reach for these utilities together with `typeof`. We can't use a value with
these type utilities; we have to use a type.

[typeof]: https://www.typescriptlang.org/docs/handbook/2/typeof-types.html

``` typescript
// method we don't have access to
import { saveCart } from "cartAPI"

// Arguments for the function
type CartCreationParams = Parameters<typeof saveCart>
// Return type for the function
type SavedCart = ReturnType<typeof saveCart>

// Now we can declare our own functions to operate
// with the external one in case some transformation
// is needed, the FormInput type is out of the scope
// of this example
const submit = (form: FormInput): SavedCart => {
  // The types will make sure we call the function
  // with the right arguments
  const cartParams: CartCreationParams = form.parse()
  return saveCart(cartParams)
}
```

When we need a set of functions external to our scope, reaching for these
utilities will keep our implementation from becoming outdated.

### Almost perfect but not quite `Required`, and `Partial`

Very useful when we need to express a different set of constraints for the same
type. Particularly useful for factories([fishery]) and methods to update parts
of an object.

``` typescript
type LineItem = {
  id: number
  sku: string
  variant: string
  ownerId?: number
}

// If we have a special line item with an owner
// we can enforce it with our types
type CreateLineItemWithOwnerParams = Required<LineItem>
// If we need to update one, we will not need all
// the properties
type UpdateLineItemParams = Partial<LineItem>
```

[fishery]: https://github.com/thoughtbot/fishery

The ability to have `variants` of the same type is what makes these two
utilities very handy.

## Takeaways

The strictness of using a type system does not have to come at the expense of
losing flexibility. We can dance around the static definitions of the problems
we are modeling, and by doing so, we'll end up with an interconnected system. A
system that will help us to stay up to date as it evolves. If having a rigorous
set of types benefits one aspect of our logic but makes it hard to operate on
others. We don't need to re-declare our definitions; we can mold them to our
needs, and by doing so, we enforce the relations in our system with the types.
