Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Event maps and type guards

Tags:

typescript

Let's say I'm building an event manager for a chat app. I've used event maps before and had a good time with it, so I will try it again.

This is the event map:

interface ChatEventMap {
    incomingMessage: string;
    newUser: {
        name: string;
        id: number;
    };
}

type EvType = keyof ChatEventMap;
type Ev<T extends EvType> = ChatEventMap[T];

So, to listen to an event, we have an on function that receives two args: the type (or name) of the event, and a callback that is called passing the event data.

It's something like this:

function on<T extends EvType>(type: T, callback: (data: Ev<T>) => void) {
    // do stuff
}

on('newUser', user => {
    console.log(user.name); // No errors!
});

But now I have the need to listen to ALL events at once. I thought of making an onEvent function that receives only a callback with the event type and it's data.

The problem is that inside the callback function, type guard is not working!

function onEvent(callback: <T extends EvType>(
    ev: { type: T; data: ChatEventMap[T] },
) => void) {
    // do stuff
}

onEvent(ev => {
    if (ev.type === 'newUser') {
        console.log(ev.data.name); // Error: Property 'name' does not exist on type 'ChatEventMap[T]'
    }
});

What am I doing wrong?

TS Playground

like image 669
Julio Hintze Avatar asked Nov 19 '25 23:11

Julio Hintze


1 Answers

Here you have working code

interface ChatEventMap {
  incomingMessage: string;
  newUser: {
    id: number;
    name: string;
  };
}

type EvType = keyof ChatEventMap;
type Ev<T extends EvType> = ChatEventMap[T];

// a bit of love for TypeScript
type Mapped = {
  [P in keyof ChatEventMap]: {
    type: P, data: ChatEventMap[P]
  }
}


function on<T extends EvType>(type: T, callback: (data: Ev<T>) => void) {
  // do stuff
}

on('newUser', user => {
  console.log(user.name); // No errors!
});

function onEvent(callback: (ev: Mapped[EvType]) => void) { }

onEvent(ev => {
  if (ev.type === 'newUser') {
    console.log(ev.data.name); // ok
  }
  if (ev.type === 'incomingMessage') {
    console.log(ev.data); // ok, string
  }
});

Sometimes, generics are not the best solution.

Please keep in mind, that next two functions are not the same:

function onEvent(callback: <T extends EvType>(ev: Mapped[T]) => void) { }

function onEvent(callback: (ev: Mapped[EvType]) => void) { }
like image 91
captain-yossarian Avatar answered Nov 23 '25 10:11

captain-yossarian



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!