Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed issues with TEvent narrowing #3252

Merged
merged 2 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/few-pianos-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed an issue with `EventFrom` not being able to extract events that had a union of strings as their `type` (such as `{ type: 'INC' | 'DEC'; value: number; }`).
5 changes: 5 additions & 0 deletions .changeset/thin-tools-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed an issue with default `TEvent` (`{ type: string }`) not being correctly provided to inline transition actions.
13 changes: 9 additions & 4 deletions packages/core/src/model.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
BaseActionObject,
Compute,
EventObject,
ExtractEvent,
MachineConfig,
Prop,
PropertyAssigner,
Expand All @@ -19,6 +18,12 @@ import {
TypegenDisabled
} from './typegenTypes';

// real `ExtractEvent` breaks `model.assign` inference within transition actions
type SimplisticExtractEvent<
TEvent extends EventObject,
TEventType extends TEvent['type']
> = TEvent extends { type: TEventType } ? TEvent : never;

export interface Model<
TContext,
TEvent extends EventObject,
Expand All @@ -28,10 +33,10 @@ export interface Model<
initialContext: TContext;
assign: <TEventType extends TEvent['type'] = TEvent['type']>(
assigner:
| Assigner<TContext, ExtractEvent<TEvent, TEventType>>
| PropertyAssigner<TContext, ExtractEvent<TEvent, TEventType>>,
| Assigner<TContext, SimplisticExtractEvent<TEvent, TEventType>>
| PropertyAssigner<TContext, SimplisticExtractEvent<TEvent, TEventType>>,
eventType?: TEventType
) => AssignAction<TContext, ExtractEvent<TEvent, TEventType>>;
) => AssignAction<TContext, SimplisticExtractEvent<TEvent, TEventType>>;
events: Prop<TModelCreators, 'events'>;
actions: Prop<TModelCreators, 'actions'>;
reset: () => AssignAction<TContext, any>;
Expand Down
15 changes: 8 additions & 7 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,7 @@ export type TransitionConfigOrTarget<
export type TransitionsConfigMap<TContext, TEvent extends EventObject> = {
[K in TEvent['type'] | '' | '*']?: K extends '' | '*'
? TransitionConfigOrTarget<TContext, TEvent>
: TransitionConfigOrTarget<
TContext,
TEvent extends { type: K } ? TEvent : never
>;
: TransitionConfigOrTarget<TContext, ExtractEvent<TEvent, K>>;
};

type TransitionsConfigArray<TContext, TEvent extends EventObject> = Array<
Expand Down Expand Up @@ -1657,7 +1654,11 @@ export type Spawnable =
export type ExtractEvent<
TEvent extends EventObject,
TEventType extends TEvent['type']
> = TEvent extends { type: TEventType } ? TEvent : never;
> = TEvent extends any
? TEventType extends TEvent['type']
? TEvent
: never
: never;

export interface BaseActorRef<TEvent extends EventObject> {
send: (event: TEvent) => void;
Expand Down Expand Up @@ -1840,8 +1841,8 @@ type ResolveEventType<T> = ReturnTypeOrValue<T> extends infer R
export type EventFrom<
T,
K extends Prop<TEvent, 'type'> = never,
TEvent = ResolveEventType<T>
> = IsNever<K> extends true ? TEvent : Extract<TEvent, { type: K }>;
TEvent extends EventObject = ResolveEventType<T>
> = IsNever<K> extends true ? TEvent : ExtractEvent<TEvent, K>;

export type ContextFrom<T> = ReturnTypeOrValue<T> extends infer R
? R extends StateMachine<
Expand Down
14 changes: 14 additions & 0 deletions packages/core/test/typeHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,20 @@ describe('EventFrom', () => {
// @ts-expect-error
acceptUserModelEventSubset({ type: 'eventThatDoesNotExist' });
});

it('should correctly extract events from events having union of strings as their `type`', () => {
const machine = createMachine({
schema: {
events: {} as { type: 'INC' | 'DEC' }
}
});

type MachineEvent = EventFrom<typeof machine, 'INC'>;

const acceptEvent = (_event: MachineEvent) => {};

acceptEvent({ type: 'INC' });
});
});

describe('MachineOptionsFrom', () => {
Expand Down
17 changes: 17 additions & 0 deletions packages/core/test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,23 @@ describe('events', () => {
}
});
});

it('should provide the default TEvent to transition actions when there is no specific TEvent configured', () => {
createMachine({
schema: {
context: {} as {
count: number;
}
},
on: {
FOO: {
actions: (_context, event) => {
((_accept: string) => {})(event.type);
}
}
}
});
});
});

describe('interpreter', () => {
Expand Down