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<T
but 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
DefinedEvents
discriminated union and abstracts the operation of checking thetype
property to distinguish between members of the union, using theExtract
utility type. Armed with these, I'd defineprocessEventOrThrowException()
like this:That's generic in both the type
T
of thetype
argument, and the typeR
of the callback return value. Now yourprocessChangeEvent()
should work as expected:and because of the
R
type inprocessEventOrThrowException()
, the return type ofprocessChangeEvent()
is inferred to benumber
:Looks good.
Playground link to code