BattlefyBlogHistoryOpen menu
Close menuHistory

Parameter objects are annoying with TypeScript

Ronald ChenAugust 22nd 2022

Function parameters are traditionally defined by position and we'll call this positional parameters.

function foo(first, second, third) {...}

foo(1, 2, 3)

Alternatively, we can gather the parameters into an object, which is unsurprisingly called parameter object.

function bar({first, second, third}) {...}

bar({first: 1, second: 2, third: 3})

Those who have used React are already familiar with parameter objects. React component props are parameter objects.

When should one use positional parameters or a parameter object?

The function factory dependency order problem

A factory function is simply a function that takes in a set of dependencies and produces something.

For example, an user repository could be created by a factory function in order to offer a better interface over the raw db API.

function UserRepository(db) {
  const repository = {
    findUserByID(userID) {
      return db.collection('users').findOne({_id: userID})
    }
  }
  return repository
}

But when factory function dependency list gets really long, it is a pain to keep the order of the parameters in sync with the call-sites.

function SomeFactory(
  db,
  userService,
  authService,
  paymentService,
  searchService
) {...}

// elsewhere in code base
const instance = SomeFactory(
  db,
  userService,
  authService,
  searchService, // oops passed searchService in as paymentService and vice versa
  paymentService
)

One solution is to introduce a parameter object.

function SomeFactory({
  db,
  userService,
  authService,
  paymentService,
  searchService
}) {...}

// elsewhere in code base
const instance = SomeFactory({
  db,
  userService,
  authService,
  searchService, // now the order does not matter
  paymentService
})

Life is good, parameter objects saved the day, but then one introduces TypeScript into the mix.

The typed parameter object problem

Typing parameter object is possible with TypeScript, but it can get annoying quickly.

function SomeFactory({
  db,
  userService,
  authService,
  paymentService,
  searchService
}: {
  db: Db,
  userService: UserService,
  authService: AuthService,
  paymentService: PaymentService,
  searchService: SearchService
}) {...}

For every parameter, we have to write out the name twice.

But why were we using parameter objects in the first place? One of the reasons was we couldn't detect the error of accidentally swapping two parameters without running our code. But that problem no longer exists in TypeScript! TypeScript knows what is the expected type of each parameter. We can go back to using positional parameters.

function SomeFactory(
  db: Db,
  userService: UserService,
  authService: AuthService,
  paymentService: PaymentService,
  searchService: SearchService
) {...}

// elsewhere in code base
const instance = SomeFactory(
  db,
  userService,
  authService,
  searchService, // TypeScript will now error, Expected PaymentSerivce, but got SearchService
  paymentService
)

This is why it is important to understand the original problem solutions intended to solve. New tools, techniques and even time change the context in which problems can be solved. New and better solutions can be found if one understands the original problem.

Do you see beyond solutions to the original problem? We like to hear from you, Battlefy is hiring.

2022

Powered by
BATTLEFY