-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nested/hierarchical States #21
Comments
I have a few questions:
I want to clarify: these are questions, not suggestions. |
XState is, of course, a huge influence on whatever we do here, but since we don't adhere to the SCXML spec, we can be more free-form in some cases.
No, transitions always belong to a state. If the user wants to represent global transitions, they can create one more level of nesting and add them under a new parent state.
Yes, it should support infinite nesting and it will be recursive.
Yes. It will recursively check for guards (we might need to update the guards to give them more information about the event and transitions), but otherwise, this transition is possible.
That's a good question. I think yes. We could require the state names to be unique, so the user could just create a transition to "wait" and it would work. As with the previous answer, we would have to fire all existing guards in the path - if any one returns false, the whole transition is denied.
That was my initial though - we would have a // All valid:
machine.value.match(/green/)
machine.value.match(/red/)
machine.value.match(/wait/)
machine.value.match(/red.wait/)
No. But the initial state might point to the parent of a nested state with its own
I see your point. I like the dot representation for the reasons mentioned above. We could:
I'm open to that. In my previous experimentation (before your type refactor), I got template literals working with this: type StatesKeys<T> = T extends { states: infer S }
? {
[K in Extract<keyof S, string>]: K | `${K}.${StatesKeys<S[K]>}`;
}[Extract<keyof S, string>]
: never; But I understand that this might not work with the current code. |
For reference, I added a branch with the experimentation on nested types I was working on before the refactor: https://github.com/cassiozen/useStateMachine/blob/dead-experimentalHierarchicalSM/src/index.tsx Some notes:
|
Sorry for the delay.
👍
This will make typing difficult but let's see.
Please do not use
Please provide a custom API to match the current state. Regexes really aren't the right tool here.
It's relatively easy to decide whether a string constant contains a dot: type HasDot<T> = T extends `${string}.${string}` ? true : false; I don't like the dot syntax because it imposes this arbitrary limitation on the keys. Not only does this make the types more complex is also a potential room for error and frustration for the user. Let's say we do use TS to enforce that state names can't contain dots. If we did that, then our users would also have to enforce this in their code which can be challenging. If the user wanted to programmatically create a state machine config, the code creating the config would have to enforce the no-dots rule everywhere. However, not enforcing the no-dots rule will leave users vulnerable to preventable and potentially hard-to-catch bugs.
Template literals can't work. The problem is recursive machines. Your The object syntax doesn't have this problem. However, now that I think about it, a simple array syntax ( |
We're not being paid for this - no need to apologize 😜.
Ok, we can revisit if the typing doesn't work.
I see your point, thanks. I will provide a custom syntax - Although, if we go with your array syntax suggestion (which I like a lot), and the user is targeting modern browsers, they could simply do: machine.value.includes('green')
machine.value.includes('red')
machine.value.includes('wait')
Agreed, I like the suggestion. Let's go for the Array syntax. |
Would you take a PR for this? Over at work we've just introduced xstate, but are now looking for a smaller state machine library with better TypeScript inference. The only feature we'd need would be nested states. |
I would definitely take a PR for this! |
@cassiozen Is there a pattern you'd suggest as a work around? |
What exactly do you want to achieve? Depending on the case you could simply flatten your state hierarchy, or use XState. |
Turns out we're not using nested states at all - we're invoking other machines and waiting on the results. I think that's already possible without any changes (thanks, React!) but maybe I'll propose something if I run into problems. |
Just wanted to give my two cents on types in context of #36, also I only skim read stuff so apologies if I missed something.
If we want, we can disallow having state names that contain dots, like txstate does.
If this is a blocker for dot syntax then it's also a blocker for array syntax :P as you can't type an infinite long array of a pattern like
Not a problem, we can enforce state names to be unique just like how txstate enforces ids to be unique I have zero opinions on these three issues in context of semantics, so y'all don't have to change any of the decisions already made if they seem right, just wanted to make it clear that types aren't a problem xD |
Again, thanks a lot for your contribuitions, @devanshj
That's perfect! If we disallow names containing dots AND require names to unique, we can keep the original plan of making it string-based. I have a semi-working implementation, but none of the typing. Let me know when you think you might have time for this and we can coordinate. |
My pleasure! You can continue with the implementation if you want, as said the implementation types need not be fully type safe. I'll start working on the user facing types too once I'm done with types for 1.0 |
Nested states are beneficial for a myriad of reasons, including:
Configuration
This is what a nested state machine configuration would look like:
State value representation:
The returned machine state would still have a string representation for the state value. Nested stated will be represented with dot syntax. For the above example, possible states are:
green
yellow
red.walk
red.wait
red.stop
Transitions & Effects
For every transition, the state machine should traverse the config three (depth-first) and find the shortest path for the transition.
Effects should be invoked on parent state only once: transitioning between child states should not re-fire parent effects.
The text was updated successfully, but these errors were encountered: