Skip to content

Commit

Permalink
fix(xstate-tree): canHandleEvent triggering side effects
Browse files Browse the repository at this point in the history
Due to the fact canHandleEvent just worked out the next state for an event and whether it had changed, the interpreter evaluates any assign actions associated with the event.

This results in any assign actions that cause side effects from being executed mistakenly.

Since `canHandleEvent` was originally created xstate has a new `state.can` method which is better suited for this and doesn't cause the assign actions to be executed.

So while you should not have side effects in your assign actions, if you do for some reason this won't trigger them when calling `canHandleEvent`
  • Loading branch information
UberMouse committed Jan 29, 2023
1 parent ac84fd7 commit 8be7aca
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 1 deletion.
43 changes: 43 additions & 0 deletions src/xstateTree.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { render } from "@testing-library/react";
import { assign } from "@xstate/immer";
import React from "react";
import { createMachine } from "xstate";

Expand All @@ -7,12 +8,54 @@ import {
buildView,
buildSelectors,
buildActions,
createXStateTreeMachine,
} from "./builders";
import { singleSlot } from "./slots";
import { delay } from "./utils";
import { broadcast, buildRootComponent } from "./xstateTree";

describe("xstate-tree", () => {
describe("a machine with a guarded event that triggers external side effects in an action", () => {
it("does not execute the side effects of events passed to canHandleEvent", async () => {
const sideEffect = jest.fn();
const machine = createMachine({
initial: "a",
states: {
a: {
on: {
SWAP: {
cond: () => true,
// Don't do this. There is a reason why assign actions should be pure.
// but it triggers the issue
actions: assign(() => {
sideEffect();
}),
target: "b",
},
},
},
b: {},
},
});

const xstateTreeMachine = createXStateTreeMachine(machine, {
selectors({ canHandleEvent }) {
return {
canSwap: canHandleEvent({ type: "SWAP" }),
};
},
View({ selectors }) {
return <p>Can swap: {selectors.canSwap}</p>;
},
});
const Root = buildRootComponent(xstateTreeMachine);
render(<Root />);
await delay(10);

expect(sideEffect).not.toHaveBeenCalled();
});
});

describe("machines that don't have any visible change after initializing", () => {
it("still renders the machines view", async () => {
const renderCallback = jest.fn();
Expand Down
2 changes: 1 addition & 1 deletion src/xstateTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export function XstateTreeView({ interpreter }: XStateTreeViewProps) {
);
const canHandleEvent = useCallback(
(e: AnyEventObject) => {
return interpreter.nextState(e).changed ?? false;
return interpreter.getSnapshot().can(e);
},
[interpreter]
);
Expand Down

0 comments on commit 8be7aca

Please sign in to comment.