statelyai / xstate-viz

Visualizer for XState machines
https://stately.ai/viz
MIT License
434 stars 102 forks source link

Mousing over a transition containing actions in the visualizer causes action code wrapped in actions.pure() to run #325

Open Steve-OH opened 2 years ago

Steve-OH commented 2 years ago

If:

Then:

You can see this behavior in the following machine:

import { actions, assign, createMachine, send } from "xstate";

let x = 0;

const machine = createMachine(
  {
    id: "my-machine",
    context: {
      n: 0,
    },
    initial: "na",
    states: {
      na: {
        on: {
          doIt: {
            target: "na",
            actions: ["inc", "doStuff"],
          },
          newCount: {
            actions: [
              (_, event) => {
                console.log(event);
              },
            ],
          },
        },
      },
    },
  },
  {
    actions: {
      doStuff: actions.pure((context) => {
        console.log("foo");
        x++;
        return send({ type: "newCount", payload: context.n, x });
      }),
      inc: assign({
        n: (context) => context.n + 1,
      }),
    },
  }
);

Run the machine, open a dev tools console, and mouse over the doIt transition to see the behavior. The behavior seems to solely in the visualizer, and the machine still works properly, so it's mostly an annoyance. But it can lead to developer confusion with the unexpected behavior, since sprinkling calls to console.log() is a common debugging technique.

davidkpiano commented 2 years ago

As the name suggests, side-effects should not be executed inside of actions.pure. The pure action is used to determine which actions will be executed in a pure way (returning an array rather than executing), and those actions can affect what the final "resolved" state of the transition will be; e.g., raised events from the raise(...) action creator may result in a different state than if you weren't to execute that action.

The workaround is to wrap side-effects in actions:

doStuff: actions.pure((context) => {
  return [
    () => { console.log('foo'); },
    send({ ... })
  ]
})