Skip to content

Commit

Permalink
Deprecate send() action creator (#3393)
Browse files Browse the repository at this point in the history
* Deprecate send

* Update types + tests

* Ensure that sendTo handles self-events (and other fixes)

* Add warnings

* Add test to read from event

* Refactor

* Revert refactor

* Refactor a different way

* Add delay support

* Update packages/core/test/actions.test.ts

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Update packages/core/test/actions.test.ts

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Update packages/core/test/actions.test.ts

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Update docs

* Fix tests

* Add test for 0 delay

* Stop reusing `send` in `raise` (#3790)

* Stop reusing `send` in `raise`

* Fixed `actionToSCXML` for `raise`

* More tests

* Create modern-frogs-wave.md

---------

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
  • Loading branch information
davidkpiano and Andarist committed Feb 12, 2023
1 parent 76aeffc commit 430986c
Show file tree
Hide file tree
Showing 17 changed files with 502 additions and 111 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-frogs-wave.md
@@ -0,0 +1,5 @@
---
"xstate": minor
---

Deprecated `send()` action creator. Instead of that, you can use `sendTo()` to send events to other actors and `raise()` to send events to the "self" actor.
7 changes: 4 additions & 3 deletions .changeset/tough-lizards-begin.md
@@ -1,11 +1,12 @@
---
"xstate": patch
'xstate': patch
---

Fixed inference for `assign` using `PropertyAssigner`, like here:

```ts
actions: assign({
counter: 0,
delta: (ctx, ev) => ev.delta,
})
delta: (ctx, ev) => ev.delta
});
```
20 changes: 20 additions & 0 deletions docs/fr/guides/actions.md
Expand Up @@ -193,6 +193,26 @@ entry: send({ type: 'SOME_EVENT' });
## Send action
::: warning
The `send(...)` action creator is deprecated in favor of the `sendTo(...)` action creator:
```diff
-send({ type: 'EVENT' }, { to: 'someActor' });
+sendTo('someActor', { type: 'EVENT' });
```
For sending events to self, `raise(...)` should be used:
```diff
-send({ type: 'EVENT' });
+raise({ type: 'EVENT' });
```
The `send(...)` action creator will be removed in XState v5.0.
:::
The `send(event)` action creator creates a special “send” action object that tells a service (i.e., [interpreted machine](./interpretation.md)) to send that event to itself. It queues an event to the running service, in the external event queue, which means the event is sent on the next “step” of the interpreter.
| Argument | Type | Description |
Expand Down
20 changes: 20 additions & 0 deletions docs/guides/actions.md
Expand Up @@ -218,6 +218,26 @@ entry: send({ type: 'SOME_EVENT' });
## Send action
::: warning
The `send(...)` action creator is deprecated in favor of the `sendTo(...)` action creator:
```diff
-send({ type: 'EVENT' }, { to: 'someActor' });
+sendTo('someActor', { type: 'EVENT' });
```
For sending events to self, `raise(...)` should be used:
```diff
-send({ type: 'EVENT' });
+raise({ type: 'EVENT' });
```
The `send(...)` action creator will be removed in XState v5.0.
:::
The `send(event)` action creator creates a special “send” action object that tells a service (i.e., [interpreted machine](./interpretation.md)) to send that event to itself. It queues an event to the running service, in the external event queue, which means the event is sent on the next “step” of the interpreter.
| Argument | Type | Description |
Expand Down
20 changes: 20 additions & 0 deletions docs/zh/guides/actions.md
Expand Up @@ -193,6 +193,26 @@ entry: send({ type: 'SOME_EVENT' });
## 发送动作(send action)
::: warning
The `send(...)` action creator is deprecated in favor of the `sendTo(...)` action creator:
```diff
-send({ type: 'EVENT' }, { to: 'someActor' });
+sendTo('someActor', { type: 'EVENT' });
```
For sending events to self, `raise(...)` should be used:
```diff
-send({ type: 'EVENT' });
+raise({ type: 'EVENT' });
```
The `send(...)` action creator will be removed in XState v5.0.
:::
`send(event)` 动作 创建者创建了一个特殊的“发送” 动作 对象,它告诉服务(即,[解释(interpret) 状态机](./interpretation.md))将该事件发送给它自己。 它在外部事件队列中,将一个事件排入正在运行的服务中,这意味着该事件将在 解释(interpret) 的下一步“步骤”上发送。
| 参数 | 类型 | 描述 |
Expand Down
28 changes: 7 additions & 21 deletions packages/core/src/StateNode.ts
Expand Up @@ -24,7 +24,8 @@ import {
toTransitionConfigArray,
normalizeTarget,
evaluateGuard,
createInvokeId
createInvokeId,
isRaisableAction
} from './utils';
import {
Event,
Expand Down Expand Up @@ -54,10 +55,7 @@ import {
InvokeCreator,
DoneEventObject,
SingleOrArray,
SendActionObject,
SpecialTargets,
SCXML,
RaiseActionObject,
ActivityActionObject,
InvokeActionObject,
Typestate,
Expand Down Expand Up @@ -1095,7 +1093,9 @@ class StateNode<
})
.concat({
type: 'state_done',
actions: doneEvents.map(raise) as Array<ActionObject<TContext, TEvent>>
actions: doneEvents.map((event) => raise(event)) as Array<
ActionObject<TContext, TEvent>
>
});

const exitActions = Array.from(exitStates).map((stateNode) => ({
Expand Down Expand Up @@ -1127,13 +1127,7 @@ class StateNode<
.map((stateNode) => stateNode.onExit)
),
this.machine.options.actions as any
).filter(
(action) =>
action.type !== actionTypes.raise &&
(action.type !== actionTypes.send ||
(!!(action as any).to &&
(action as any).to !== SpecialTargets.Internal))
);
).filter((action) => !isRaisableAction(action));
return actions.concat({ type: 'stop', actions: stopActions });
}

Expand Down Expand Up @@ -1324,15 +1318,7 @@ class StateNode<

const [raisedEvents, nonRaisedActions] = partition(
resolvedActions,
(
action
): action is
| RaiseActionObject<TContext, TEvent>
| SendActionObject<TContext, TEvent, TEvent> =>
action.type === actionTypes.raise ||
(action.type === actionTypes.send &&
(action as SendActionObject<TContext, TEvent>).to ===
SpecialTargets.Internal)
isRaisableAction
);

const invokeActions = resolvedActions.filter((action) => {
Expand Down
63 changes: 50 additions & 13 deletions packages/core/src/actions.ts
Expand Up @@ -41,6 +41,7 @@ import {
EventFrom,
AnyActorRef,
PredictableActionArgumentsExec,
RaiseActionOptions,
BaseActionObject,
LowInfer
} from './types';
Expand Down Expand Up @@ -159,32 +160,57 @@ export function toActivityDefinition<TContext, TEvent extends EventObject>(
* @param eventType The event to raise.
*/
export function raise<TContext, TEvent extends EventObject>(
event: Event<TEvent>
):
| RaiseAction<TContext, TEvent>
| SendAction<TContext, AnyEventObject, TEvent> {
if (!isString(event)) {
return send(event, { to: SpecialTargets.Internal });
}
event: Event<TEvent> | SendExpr<TContext, TEvent, TEvent>,
options?: RaiseActionOptions<TContext, TEvent>
): RaiseAction<TContext, TEvent> {
return {
type: actionTypes.raise,
event
event: typeof event === 'function' ? event : toEventObject<TEvent>(event),
delay: options ? options.delay : undefined,
id: options?.id
} as any;
}

export function resolveRaise<TContext, TEvent extends EventObject>(
action: RaiseAction<TContext, TEvent>
action: RaiseAction<TContext, TEvent>,
ctx: TContext,
_event: SCXML.Event<TEvent>,
delaysMap?: DelayFunctionMap<TContext, TEvent>
): RaiseActionObject<TContext, TEvent> {
const meta = {
_event
};
const resolvedEvent = toSCXMLEvent(
isFunction(action.event)
? action.event(ctx, _event.data, meta)
: action.event
);

let resolvedDelay: number | undefined;
if (isString(action.delay)) {
const configDelay = delaysMap && delaysMap[action.delay];
resolvedDelay = isFunction(configDelay)
? configDelay(ctx, _event.data, meta)
: configDelay;
} else {
resolvedDelay = isFunction(action.delay)
? action.delay(ctx, _event.data, meta)
: action.delay;
}
return {
...action,
type: actionTypes.raise,
_event: toSCXMLEvent(action.event)
_event: resolvedEvent,
delay: resolvedDelay
} as any;
}

/**
* Sends an event. This returns an action that will be read by an interpreter to
* send the event in the next step, after the current step is finished executing.
*
* @deprecated Use the `sendTo(...)` action creator instead.
*
* @param event The event to send.
* @param options Options to pass into the send event:
* - `id` - The unique send event identifier (used with `cancel()`).
Expand All @@ -204,6 +230,8 @@ export function send<
type: actionTypes.send,
event: isFunction(event) ? event : toEventObject<TSentEvent>(event),
delay: options ? options.delay : undefined,
// TODO: don't auto-generate IDs here like that
// there is too big chance of the ID collision
id:
options && options.id !== undefined
? options.id
Expand Down Expand Up @@ -296,7 +324,7 @@ export function sendTo<
TEvent extends EventObject,
TActor extends AnyActorRef
>(
actor: string | TActor | ((ctx: TContext) => TActor),
actor: string | TActor | ((ctx: TContext, event: TEvent) => TActor),
event:
| EventFrom<TActor>
| SendExpr<
Expand Down Expand Up @@ -687,7 +715,16 @@ export function resolveActions<TContext, TEvent extends EventObject>(
) {
switch (actionObject.type) {
case actionTypes.raise: {
return resolveRaise(actionObject as RaiseAction<TContext, TEvent>);
const raisedAction = resolveRaise(
actionObject as RaiseAction<TContext, TEvent>,
updatedContext,
_event,
machine.options.delays as any
);
if (predictableExec && typeof raisedAction.delay === 'number') {
predictableExec(raisedAction, updatedContext, _event);
}
return raisedAction;
}
case actionTypes.send:
const sendAction = resolveSend(
Expand All @@ -713,7 +750,7 @@ export function resolveActions<TContext, TEvent extends EventObject>(
if (blockType === 'entry') {
deferredToBlockEnd.push(sendAction);
} else {
predictableExec?.(sendAction, updatedContext, _event);
predictableExec(sendAction, updatedContext, _event);
}
}

Expand Down

0 comments on commit 430986c

Please sign in to comment.