I try to create typings for events that will be processed in xstate.
Currently xstate calls the processChangeEvent function with a DefinedEvent parameter. This typing can't be changed.
The processChangeEvent should always be called with a change event therefore I would like to achieve a way to check if the given event is a change event or else throw an error.
Everything works inside an if statement. The typescript compilers realizes the CHANGE event type and therefor I can access the value attribute.
export type Event<TType, TData = unknown> = { type: TType } & TData;
export type Value<TType> = { value: TType };
export type DefinedEvents = Event<'INC'> | Event<'DEC'> | Event<'CHANGE', Value<number>>;
function processChangeEventOK(event: DefinedEvents) {
if (event.type === 'CHANGE') {
return event.value; // NO COMPILER ERROR :-)
} else {
throw new Error(`Event must be of type CHANGE but is ${event.type}`);
}
}
The problem is that I need this a lot of times. Therefore I tried to extract the logic inside the processEventOrThrowException function. I know that somehow the callback must be differently typed Event<Tbut I wouldn't know how?
function processChangeEvent(event: DefinedEvents) {
return processEventOrThrowException(event, 'CHANGE', (castedEvent) => {
return castedEvent.value; // COMPILER ERROR :-(
});
}
function processEventOrThrowException<TType>(event: Event<any>, type: string, callback: (castedEvent: Event<TType, unknown>) => any) {
if (event.type === type) {
return callback(event);
} else {
throw new Error(`Event must be of type CHANGE but is ${event.type}`);
}
}
Let's define the following helper types and user-defined type guard function:
This takes your
DefinedEventsdiscriminated union and abstracts the operation of checking thetypeproperty to distinguish between members of the union, using theExtractutility type. Armed with these, I'd defineprocessEventOrThrowException()like this:That's generic in both the type
Tof thetypeargument, and the typeRof the callback return value. Now yourprocessChangeEvent()should work as expected:and because of the
Rtype inprocessEventOrThrowException(), the return type ofprocessChangeEvent()is inferred to benumber:Looks good.
Playground link to code