cassiozen / useStateMachine

The <1 kb state machine hook for React
MIT License
2.38k stars 47 forks source link

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

Closed threehams closed 2 years ago

threehams commented 3 years ago

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.

threehams commented 3 years ago

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

devanshj commented 3 years ago

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

Though it does have something similar

cassiozen commented 3 years ago

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.

threehams commented 3 years ago

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.

cassiozen commented 3 years ago

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...

threehams commented 3 years ago

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?