cassiozen/useStateMachine

Suggestion: mayTransition function to check if a `send()` would pass guards.

threehams opened this issue · 6 comments

What

Return a function state.mayTransition that allows a user to ask "if I sent this event, would it pass the guards on transitions?"

Usage:

const [state, send] = useStateMachine()({
  initial: "inactive",
  states: {
    inactive: {
      on: {
        ENABLE: "active",
      },
    },
    active: {
      on: {
        DISABLE: {
          target: "inactive",
          guard: () => false,
        },
      },
    },
  },
});

state.mayTransition("ENABLE"); // true
send("ENABLE");
state.mayTransition("DISABLE"); // false

Why

Allow selectively rendering or disabling UI input - such as navigation or submission buttons - based on whether the intended event would pass all guards for the transition. (This would be lazily executed, in case someone's machine has side effects in guard().)

From quick testing, this would be a very small code increase.

xstate?

As far as I can tell, xstate doesn't support this, but I don't know why - or I don't know how to search for it. It's normal for Ruby state machines, though.

Name

mayTransition might not be a great name! I'm not sure what makes sense... maySendEvent? wouldEventBeAccepted? Naming is hard.

(I could work on this today if the design makes sense, and it doesn't interfere with other features.)

As far as I can tell, xstate doesn't support this, but I don't know why

Though it does have something similar

I understand the proposal, but my concern is that guards might be highly dynamic - maybe they're using the context, a timer or any other crazy mechanisms to allow or not a transition: in this scenario, it might be challenging to come up with an implementation of mayTransition that works.

Besides the implementation, which I can figure out, do you have any design concerns?

This probably wouldn't work well with async type guards, for example, but I don't know if there are any plans for that.

No, I think it makes sense, I just think it's impossible to do because guards aren't pure.

Imagine that a guard is implemented like this:

guard: () => Math.random()>0.5? true: false;

Granted, this is an extreme example but goes to show that mayTransition would be unreliable.

We could require guards to be pure, but even then, it receives context and the context might be constantly changing as well...

I've got a patch at work where this is working fine, including with updating context values. Guards can be non-deterministic, sure... but doesn't that just warrant a warning?