Posts tagged "typing"

Typescript function parameter type

A previous post mentioned the Typescript ReturnType utility type. It is really handy to retrieve the return type of a function.

But there is also a type to retrieve the parameter list of a function. Meet Parameters<T>.

const add = (first: number, second: number) => first + second;

const cache = <T extends (...args: any) => any>(func: T) => {
  const cacheObject = {};
  return (...args: Parameters<T>) => {
    const hashedArgs = JSON.stringify(args);
    if (cacheObject.hasOwnProperty(hashedArgs)) {
      return cacheObject[hashedArgs];
    } else {
      return (cacheObject[hashedArgs] = func(...args));
    }
  };
};

See the full example here.

The code can be found at lib/lib.es5.d.ts:

/**
 * Obtain the parameters of a function type in a tuple
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

Enhance Redux props with ReturnType

The following example is built in the offical Redux documentation:

import { AppState } from './store'

import { SystemState } from './store/system/types'

import { ChatState } from './store/chat/types'

interface AppProps {
  chat: ChatState
  system: SystemState
}

const mapStateToProps = (state: AppState) => ({
  system: state.system,
  chat: state.chat
})

Here, SystemState and ChatState are imported and used manually.

But as AppState is correctly typed, we can use our beloved ReturnType instead:

import { AppState } from './store'

type AppProps = ReturnType<typeof mapStateToProps>

const mapStateToProps = (state: AppState) => ({
  system: state.system,
  chat: state.chat
})

Check out this CodeSandbox for a runnning example.


Typescript json validation with io-ts

There's a Typescript library called io-ts that can help to strong-type json data fetched from the server and at the same time provide static typescript typing.

Problem

Imagine we have this line that fetches some data from an endpoint:

const employee = await fetchEmployee();

employee will probably have the any type. If we knew the shape of the employee object, we could create a type and cast employee:

type Employee {
  firstName: string;
  lastName: string;
}

const employee = await fetchEmployee() as Employee;

But now we are assuming the shape of employee, and if it changes in future versions of the backend, it can lead to annoying runtime errors such as accessing properties on undefined objects, which can be hard to track. We could validate it with a lib such as ajv, but we wouldn't be able to have a single source of truth.

Solution

With io-ts we can define a type like this:

import * as t from 'io-ts';

// The runtime type we will use to validate the data fetched from the server
const Employee = t.type({
  firstName: t.string,
  lastName: t.string,
});

// The static type. The above runtime type acts as the single source of truth
type Employee = t.TypeOf<typeof Employee>;

// Now we can do this (in pseudo-code)
const employee = Employee.decode(await fetchEmployee());

console.log(employee.firstName);  // works
console.log(employee.foo);        // typescript compile error

Now employee is correctly typed, and the shape of the data is validated at runtime.

Implementing the pseudo-code

The lib is great, but the documentation found in the repository's README is a little confusing. The easiest way I found to just validate data and throw if the shape is incorrect is like this:

import { getOrElse } from "fp-ts/lib/Either";  // io-ts has fp-ts as a peer dependency
import { failure } from "io-ts/lib/PathReporter";

const toError = (errors: any) => new Error(failure(errors).join('\n'));
const employee = getOrElse(toError)(Employee.decode(await fetchEmployee()));

if (employee instanceof Error) {
  throw employee;
}

console.log('the first name is', employee.firstName)

This steps can be easily extracted to a helper function.


Typescript ReturnType

Typescript includes several useful utility types to enhance the type declarations of your code-base.

The ReturnType function is one of my favorite ones, as it helps reduce type definition duplication.

Suppose you have the following function definition:

type IsInText = (
  text: string
) => (
  term: string,
  minCount: number,
  maxCount?: number,
  caseSensitive?: boolean
) => boolean

Now we want to write a function allTermsInText, that takes the function returned by isInText as an argument. It should be used like:

allTermsInText(["Typescript", "awesome"], isInText("Typescript is awesome!"))

Here is the definition without the utility type:

type AllTermsInText = (
  terms: string[],
  search: (
    term: string,
    minCount: number,
    maxCount?: number,
    caseSensitive?: boolean
  ) => boolean
) => boolean

And here the same function definition, but using ReturnType for the parameters:

let AllTermsInText = (terms: string[], search: ReturnType<IsInText>) => {
  return !terms.find(term => !search(term, 1))
}