DarkLite1
DarkLite1

Reputation: 14755

How to tell typescript to accept an empty array or a function

Consider the following code:

const subscriptions: { [key: string]: [() => void]  } = {}

interface ISubscription {
  eventName: string
  callback: () => void
}

export function unsubscribe({ eventName, callback }: ISubscription) {
  if (!subscriptions[eventName]) { return }
  const index = subscriptions[eventName].findIndex((l) => l === callback)
  if (index < 0) { return }
  subscriptions[eventName].splice(index, 1)
}

export function subscribe({ eventName, callback }: ISubscription) {
  if (!subscriptions[eventName]) {
    subscriptions[eventName] = []
  }
  subscriptions[eventName].push(callback)
  return () => unsubscribe({ eventName, callback })
}

The issue typescript reports is:

TS2741: Property '0' is missing in type '[]' but required in type '[() => void]'.

Which comes from trying to assign an empty array to the subscriptions:

if (!subscriptions[eventName]) {
  subscriptions[eventName] = []
}

This can be fixed by defining the interface to accept an empty array but then there's an issue when assigning a callback:

const subscriptions: { [key: string]: [() => void] | [] } = {}

TS2345: Argument of type '() => void' is not assignable to parameter of type 'never'.

A workaround would be:

const subscriptions: { [key: string]: [() => void] } = {}

if (!subscriptions[eventName]) {
  subscriptions[eventName] = [callback]
}
else {
  subscriptions[eventName].push(callback)
}

What is the correct way to be able to set it to an empty array and then assign the callback? I tried using the question mark too but I can't seem to get it right. Thank you for your help.

Upvotes: 0

Views: 2686

Answers (3)

Dane Brouwer
Dane Brouwer

Reputation: 2972

Pretty straight-forward solution

type TCallBackArray = (() => void)[];
interface ISubscriptions { 
    [key: string]: TCallBackArray
};

const subscriptions: ISubscriptions = {}

Where TCallBackArray is defined as an array of functions, and ISubscriptions is defined as an indexer.

Upvotes: 1

Serabe
Serabe

Reputation: 3924

This is a common misconception. [() => void] is a tuple of one element, and that element needs to satisfy the type () => void. If you want an array, let it be empty or not, with one or multiple elements, the correct type is (() => void)[].

The following expressions are accepted by (() => void)[]:

  1. []
  2. [() => { doSomething(); }]
  3. [() => { doSomething(); }, () => { doSomethingElse(); }]

While only the expression number 2 is accepted by [() => void].

If you want either an empty array or a tuple of one element, you can just do [] | [() => void]

Upvotes: 2

messerbill
messerbill

Reputation: 5639

You can simply define it as an "array of type":

type Subscriptions = {
  [key: string]: Array<()=>void>
}

then:

const subscriptions: Subscriptions = {}
if (!subscriptions[eventName]) {
  subscriptions[eventName] = []
}

Upvotes: 1

Related Questions