Replies: 5 comments 14 replies
-
I used to use |
Beta Was this translation helpful? Give feedback.
-
Typing is probably the biggest pain point when using XState. This is a combination of of the terrible error messages and complex typing under the hood for XState. Models make some things easier at runtime such as handling generics - though they make it harder to extract those types later. I feel that the TS documentation should be flushed out more with richer examples and list well known pain points. Even an extra page for "Here are dozens of examples of how to do typing" could help alleviate a lot of developer stress from having to piecemeal it themselves. Some of it is Googling and understanding core TypeScript would help, but parts of it are also understanding when the type errors are an artifact of XState or TypeScript (again, see bad error messages). Here is a naïve example using the example factories above: const SEND_REQUEST = <TRequest>(request: TRequest) => ({ type: 'SEND_REQUEST', request });
const PAGE = () => ({ type: 'PAGE' });
type MachineEvents<TRequest> =
| ReturnType<typeof SEND_REQUEST>
| ReturnType<typeof PAGE>
// TypeScript shows type:
// type MachineEvents = {
// type: string;
// request: unknown;
// } | {
// type: string;
// } |
Beta Was this translation helpful? Give feedback.
-
I agree with deprecating it. Never fully adopted it, because it never fully solved the TS problems I have. Typegen is closer to be type perfect. |
Beta Was this translation helpful? Give feedback.
-
This worries me a bit. Judging from recent releases and now this message, it looks like maintainers of this project put a lot of their focus on external gadgets, like VSC plugins, rather than extending and refactoring the library itself. If true, the direction of creating a tight dependency between one specific IDE and the library, renders, in my opinion, the latter unusable, or at least not trustworthy. One could ask a question whether xstate is an open and independent library or part of the VS Code ecosystem? Automation introduced by typegen looks cool on paper, but developers should be able to achieve the same outcome without being forced to use it. Have you considered releasing a cli for example? I have started using "createModel" approach because it solved several problems with TS, and one big one among others - verbosity. Although not ideal, this solution allows to, well, nomen omen, model a state machine with its context and events rather quickly, without having to write a tonne of unnecessary code. Personally, I would welcome expansion of in-code solutions (factories/helpers/etc) to solve common Typescript related problems more, than having to rely on my IDE to do it for me. Mainly, because I write in VIM... While on it, I have recently noticed an influx of compatibility breaking changes in minor (!!!) releases and even patches (the last one with version 4.30.2.). These releases predominantly address bugs in relation to typegen, inadvertently breaking otherwise production ready solutions where typegen is not being used... At this point our team had hardcoded working version of the package, because we can't trust even the slightest updates. It's not a good look for otherwise brilliant library, to be honest. |
Beta Was this translation helpful? Give feedback.
-
@Andarist thanks for pointing this thread to me. When I have just started using XState, I think the model API is what I need to declare all my types/events, but soon I switched back to the Redux style because I think the classic style is better. Currently, I'm using the typesafe-actions with my ducks for managing all my events/types and it is quite clear, easy to use, and straightforward. I'm very happy with them. (here's my WIP PR in case anyone interested) So I will vote for deprecation because this feature is not the core feature of XState. |
Beta Was this translation helpful? Give feedback.
-
Model
Let's start by listing what is the responsibility of the model today and what can be done with it.
assign
(it's bound toTEvent
type)createMachine
reset
action that allows one toassign
the initial context from point 1Features recap/purpose
Event factories
We came to the conclusion that event factories can be somewhat cumbersome to work with. The
TEvent
type that is produced based on them (through TS inference) is very verbose and, at times, very hard to read. TS is somewhat known for very hard-to-read errors and thisTEvent
can compound this problem. The types machinery used to infer this type is very clever but it's also very hard to debug and modify. One of the main problems here is that the wholeTEvent
becomes "anonymous" (this also applies to every member of its union) and we can't assign any helpful labels/aliases that would be used by TS-based IDE tooltips etc.In addition to that, we've found out that it can lead to inconsistencies in the codebase because there is no way to restrict people to only using this way of sending events. This could potentially be solved with a linter.
On top of that, declaring send actions utilizing the model for the parent-child communication can lead to module cycles and even runtime errors as it's possible to call a model's event factory during importing module initialization before the imported module, containing the model, has a chance to be initialized (hi @erikras 👋).
All in all, this doesn't quite "fit" that well with JavaScript language. Note that such event factories are common in, for example, Scala and the Akka framework. The important distinction though is that the language design there is different and the critique outlined here doesn't quite apply to its context.
Initial context
Because TS doesn't allow for "partial inference" and model is often used with event factories that require us to stay in the inference bounds, we end up declaring context type using unsafe assertions.
Type assertions are a very useful language/compiler feature. However, they are not meant for type declarations, and using them like that can lead to an increased number of runtime errors. I think that our job is to promote type-safety in the design of our API and having to rely on type assertions is hurting that goal.
Prebound functions
This is somewhat a nice feature of the model but its main appeal is that it enables us to workaround the mentioned partial inference limitation of TypeScript.
We are working hard on improving the TS story with an additional typegen tool and, with that in place, the main appeal of this "feature" might become redundant. This is especially true for the
model.assign
because we plan to precompute possible event types and encourage the use of the second argument of thecreateMachine
to provide implementations for actions used in the machine config object using "string references" (aka "named actions").Context reset
I believe that this is useful at times but it can also be misunderstood by users. The problem, and important realization about the model's API, is that the whole thing is unrelated to the final machine. It's just a container for some initially-defined & static values. This means that it can only reset to the model's initial context but it can't quite reset to the "true" initial context with which the machine has been started.
Possible deprecation
We are considering deprecating the model. It's important for us to hear out the community feedback before going with this further.
Note that for those who like what the model currently has to offer we could move it to a separate package that could still be used by your codebases. It just wouldn't be the "default" way of interacting with the running machines etc.
We could also consider only deprecating the model factories but:
Also note that it's pretty easy to declare event factories with vanilla TypeScript/JavaScript:
One additional argument for deprecating the model API is that it makes it harder to sync event schemas between the code and the visual editor that we are working on.
Beta Was this translation helpful? Give feedback.
All reactions