-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[Feature] Expose Invoked Services #424
Comments
Coming from this conversation, I see that this RFC helps with the issue of getting the state of invoked services out of the parent machine so that external behavior might be derived from them (such as rendering todos in the example above). |
@LunarLanding In your view, how would the API look if XState were capable of managing dynamic invocations, created on demand, etc.? For example, how would you want to represent the TodoMVC example with an alternative architecture (something less complicated than the game you presented)? |
I don't know the specifics of the todoMVC, but the key change would be invoking as an action. const singleTodoMachine = Machine({
id: 'todo',
initial: 'active',
context:{text:''},
onEntry:'linkRenderer',
states: {
active: {
on:{
commit:assign({text:(ctx,event)=>event.text}),
deleted:'deleted'
}
},
deleted: {
type:'final'
}
}
});
const todosMachine = Machine({
id: 'todos',
initial: 'active',
context:{todos:[]}
states: {
active: {
on: {
addTodo:{
cond:???
internal:true,
actions:[
invoke({
src:'todo'
id:(ctx,event)=>event.id
}),
assign({todos:(ctx,event)=>ctx.todos.append(event.id)})
]
}
}
},
inactive: {}
}
}) Rendering:
Adding invoke as an action: A limitation: In my game code right now I have a class User, with a static registry of instances, and each instance contains a interpreter for a UserMachine. The gameMachine keeps in its context a set of references to User machines, and uses User.registry to get their interpreters and send them messages. |
@LunarLanding Related proposal: #428 (please comment, would love to see your thoughts re: a potential API!) |
@davidkpiano I think you can close this issue, no? In let [state, send] = useMachine(fooMachine)
let [childState, childSend] = useService(state.children.get("someChildService")) |
@hnordt is that really working? For me, I have no children property on my state (using typescript, men even casting to any gives no children property). There is however a children property on the service instance. const [currentState, send, service] = useMachine(parentMachine);
let [childState, childSend] = useService((service as any).children.get("childMachineId")); Trying to access an invoked child machine like this gives an error in all the states for which the child machine is not invoked. I am using Any idea of how I can listen to invoked child machine transitions? I opened an issue with the question here: #637 |
There is no |
@davidkpiano ....still trying to wrap my head around this. Perhaps you can enlighten me conceptually. If I have nested machines, which invokes other machines and so on. This is a very nice setup to break down complex interactions and UIs. But I cant seem to grasp how I actually connect the machines to actual ui rendering. Using the react hooks to get the current state works nicely, as long as I don´t want to query a nested machines state (as discussed in this issue). A couple of questions that comes to my mind are:
As a concrete excample, we could take your example with the text format state machines. Say I have one root machine and three paralell machines invoked from the root machine (bold, italic, underline). Now I would want to render a toolbar with buttons toggled or not depending on the states in the child machines. How would I do that? Sorry for throwing it all out there but my mind hurts right now. :-) |
When this feature goes in, you pass the service as props: const App = () => {
const [current, send] = useMachine(...);
const { someService } = current.children;
return (
<SomeComponent service={someService} />
);
}
const SomeComponent = ({ service }) => {
const [current, send] = useService(...);
// ...
}
Yes, you can do that, or you can idiomatically use callback props in actions (e.g.,
Currently, you can grab child services via the parent service: const [current, send, service] = useMachine;
service.children; // Map()
service.children.get('someService'); // child service
Right, that's more meant for internal use, but nothing's stopping you right now.
With
Depends how you want to model it. If the nested state is closely tied to the parent state, then make it one machine. If it should be loosely coupled, make it an invoked machine. EDIT: I'm making a Subreddit example (same one as the Redux docs) to highlight how these invoked machines play together. |
Thank you so much for these answers and your time. I will try to get a bit further with the answers. One thing though, grabbing the service by using your example does not work for me. It seems like the grabbed service is not updated or I'm looking at a previous instance or something. const [currentState, send, service] = useMachine(rootMachine);
const childMachine= (service as any).children.get('childMachineId');
const childMachineStatev= childMachinev? childMachine.state : undefined;
console.log(currentState); // Logs correct state, the one that invokes childmachine
console.log(childMachineState); // Logs wrong state, child machine have progressed further that this shows. Its like its another instance or not receiving updates. |
Can you make a reproducible example? Also note the typos in your code snippet ( |
Here is an example attached (an empty create-react-app + xstate). Where you can see that the label does not render at all. Even though I can see the object if I log them. This also surprises me....is it some typescript thing going on here? |
I managed to get a bit further with the following steps: I installed xstate 4.7.0-rc2 then I could get things working using your instructions above and the following code: const [current, send, service] = useMachine(rootMachine);
const childService = service.children.get('childMachineId');
return (
<div className="center">
{ childService &&
<StateLabel service={childService} />
}
</div>
) |
@davidkpiano Any plans to add the |
Not yet, it might not be necessary, since you can accomplish the same thing by checking for the existence of |
@kyleconkright it would also be nice if u could tell us more about your use case. Maybe we'd have some recommendations on how you could do what you want |
Thanks. I'm using xstate to manage a flow in Angular. I have an outer module with xstate initializing some values and putting them into context. Then, I conditionally invoke one of two possible machines. Each of these machines are in their own Angular module as well. First time users will go through machine A and then be placed into machine B. Returning users will be placed straight into machine B. If I listen to a child machine in a child module, I get a new instance of the machine and not the version invoked by the xstate parent. I think I can use @davidkpiano's suggestion for getting the correct machine and not a new instance. Am I making sense here? Edit: Looks like I need the concept of |
Bug or feature request?
Feature
Description:
When services are invoked in a parent service, their state is currently kept hidden to their machine. This is by design, we can traverse this invocation tree and "query" the state of each invoked service by subscribing.
(Feature) Potential implementation:
Consider a parent
todosMachine
like this:Then, assuming
todosService
is implemented as aMachine
, we can subscribe to its state the same way you would do with any other service:You can imagine that this would be useful in a React app:
The new APIs for
xstate
(core) would be:service.onInvoke(childService => { ... })
service.children
- already exists, but mapping would provide the actual services instead of thin interfaces. Only to be used for debugging.The new APIs for
@xstate/react
would be:useService(service)
- same asuseMachine()
but with a running serviceuseChildService(service, id)
- same asuseMachine()
but with a running child service whenever the child is invokedThis is tentative and I'm still researching to see how this works with the Actor model. Would love feedback!
The text was updated successfully, but these errors were encountered: