Comments (5)
The idiomatic way to achieve this would be to hoist your machine to some common parent and then pass down the created
actorRef
using the provide/inject API ( https://vuejs.org/guide/components/provide-inject ).
Indeed, this would be a suitable workaround and do exactly what I need without any changes needed in @xstate/vue
. I overlooked this alternative because I'm much more used to use hooks instead when possible, and this is less ideal for me since it would force me to use two different ways of interacting with machine depending on if they should be shared or not, but it would work just fine.
useMachine
/useActor
starts separate instances so that's not what you are looking for.
[...]
This application doesn't run a single machine but rather two machines - each one is scoped to its own component.
I understand that and I never expected that simply using useMachine
/useActor
out of the box would create a shared machine/actor across components. I guess I did not make it clear in the issue that I was wrapping the useMachine
hook inside a function that would make it shareable, so to better reflect my intent your React snippet would look more like this :
const counterMachine = setup({}).createMachine({ /* ... */ })
const useSharedCounterActor = () =>
createSharedHook(() => useActor(counterMachine));
const First = () => {
useSharedCounterActor();
return null;
};
const Second = () => {
useSharedCounterActor();
return null;
};
const App = () => {
return (
<>
<First />
<Second />
</>
);
};
Now First
and Second
would share access to the same instance of the counter machine, instead of having each their own separate instance.
In Vue, this is possible thanks to effect scopes. To sum it up, every reactive effects (ref
, computed
, watch
, etc...) are bound to a scope, and are disposed/collected when the scope is terminated. This is how Vue keep track of everything and knows when it should stop and garbage collects what's not needed anymore.
By default, each component define its own scope, so reactive effects are disposed when their parent component is unmounted. However, the effectScope
API allows to define a custom scope that is not bounded to a particular component, and it can be used to create a shareable hook that will keep track of the places where its reactive effects are needed (i.e its suscriber), and can stop itself when all the related components have been unmounted.
There is an example of how it can be implemented in the RFC that introduced this API if you want to learn more about it, but what's important here is that creating shareable hooks is possible in Vue 3.2.x and beyond. There is even an utility function provided by a popular Vue library that allow to do exactly that, without having to code the logic yourself.
That's why I expected at first that by wrapping useMachine
/useActor
inside a createSharedComposable
utility, I would be able to reuse the same machine across components. However, because useActorRef
calls directly onBeforeUnmount
, its reactive effects are stopped independently from its parent scope, which breaks the shared instance when a component unmount, for example when changing routes. On the repro, the "fixed" version of the machine hook/composable uses a patched useActorRef
that implements the effectScope
API , which is why it is working as intended vs the out-of-the-box version.
Personally, I still consider the current behavior of @xstate/vue
to be a bug, or at least unexpected behavior, but as you pointed out a workaround is available. I would understand if you considered that it makes fixing this issue a noop or at least not your priority, especially since it would require to upgrade the vue version to at least 3.2.x, which is a breaking change.
I hope that I was able to clear up any misunderstanding and that the explanations I gave were clear enough, if that's not the case feel free to ask me any precisions you need.
Thank you for your time and for maintaining this project. 😊
from xstate.
Unless I'm missing something, provide/inject
is easy until you want to add type safety to it, which causes some indirections that add boilerplate and in the end force you to type-cast something (either the injection key and/or the provided value) :
// keys.ts
import type { InjectionKey } from 'vue'
import { Actor } from 'xstate'
import { authMachine } from './actors/authMachine'
export const authKey = Symbol('auth') as InjectionKey<Actor<typeof authMachine>>
// Parent component
import { authMachine } from 'src/actors/authMachine'
import { authKey } from './keys'
import { provide } from 'vue'
const { actorRef } = useActor(authMachine.provide(/* ... */)
provide(authKey, actorRef)
// Child component
import { authKey } from 'src/keys'
import { inject } from 'vue'
const throwError = () => { throw new Error() }
// typeof inject(authKey) is Actor<typeof authMachine> | undefined
// which is not usable, so I have to narrow it like this :
const authActor = inject(authKey) || throwError()
Please correct me if I'm doing something wrong here, but IMHO an API using shared composables would be better suited and more appealing to existing Vue developers, as proposed by @IonianPlayboy.
from xstate.
The idiomatic way to achieve this would be to hoist your machine to some common parent and then pass down the created actorRef
using the provide/inject API ( https://vuejs.org/guide/components/provide-inject ). useMachine
/useActor
starts separate instances so that's not what you are looking for.
I'll use a React code snippet here, since that's what I'm familiar with:
const counterMachine = setup({}).createMachine({ /* ... */ })
const First = () => {
useActor(counterMachine)
return null
}
const Second = () => {
useActor(counterMachine)
return null
}
const App = () => {
return (
<>
<First />
<Second />
</>
)
}
This application doesn't run a single machine but rather two machines - each one is scoped to its own component.
from xstate.
Please correct me if I'm doing something wrong here, but IMHO an API using shared composables would be better suited and more appealing to existing Vue developers, as proposed by @IonianPlayboy.
I would gladly welcome a contribution for this.
from xstate.
Unless I'm missing something, provide/inject is easy until you want to add type safety to it, which causes some indirections that add boilerplate and in the end force you to type-cast something (either the injection key and/or the provided value)
To be fair, that's kinda on Vue's APIs. I agree that this is somewhat awkward and I prefer how the same thing is done in React.
In @xstate/react
we have createActorContext
that binds a couple of pieces together (type-wise). A similar approach could be used here - that would hide inject
/provide
dance behind the scenes.
Please correct me if I'm doing something wrong here, but IMHO an API using shared composables would be better suited and more appealing to existing Vue developers, as proposed by @IonianPlayboy.
That's somewhat similar to createActorContext
idea. I'm not sure how the @IonianPlayboy's idea would be implemented though. It refers to createSharedHook
but it doesn't go into its details and - from what I can tell - this is not something that Vue provides.
from xstate.
Related Issues (20)
- Bug: @xstate/inspect has 4x peer dependency HOT 2
- Bug: type error when referencing guard with `check` inside `setup` HOT 1
- Bug: UnifiedArg drops the type of emitter from its "self" property HOT 1
- Bug: Spawned child machines should inherit the parent's clock HOT 1
- Bug: Type Error with Actions outside of Setup()
- Bug: Spawn and TS error HOT 1
- Bug: typegen.ts ist incomplete when moving state node configs to separate files HOT 3
- Bug: Promise actor error handling doesn't work as documented HOT 3
- Bug: types broken with assign HOT 2
- Bug: supplying generic input argument to setup actors for stubbing breaks invoke type inference
- Bug: When child state transitioned to final state, did not trigger onDone event and transition to next state
- Bug: [@xstate/store] typings broken HOT 2
- Bug: Bug with After event on restoring snapshot
- Bug: [Typescript] event target doesn't get restricted to declared state HOT 3
- Bug: HOT 3
- Methods like `provide` are lost when implementing Higher Level Actor Logic HOT 1
- Bug: system.get(id) not working after restoring a snapshot HOT 2
- Bug: Importing Xstate in a React-Native android project errors HOT 1
- Bug: History state in parallel machines is changing states unexpectedly HOT 2
- Bug: State entry action's actor value mismatch HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from xstate.