Skip to content

Commit 51cfe5e

Browse files
authored
fix: always walk machines once (#115)
1 parent 46bf713 commit 51cfe5e

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

.changeset/shy-experts-wink.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"xstate-component-tree": patch
3+
---
4+
5+
Fix an issue where in specific situations child trees would not be built.
6+
7+
If a child machine has an `invoke` that immediately triggers a no-op event, the `ComponentTree` instance wouldn't actually walk that child machine for components to render. This was due to an interesting interaction between the xstate `.changed` property and when `invoke`s within the statechart are run.
8+
9+
Now whenever the `ComponentTree` sees a new machine it hasn't walked is running, it will walk it.

src/component-tree.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,17 +208,19 @@ class ComponentTree {
208208
// Callback for statechart transitions to sync up child machine states
209209
_onState(path, state) {
210210
const { changed, children } = state;
211+
const { _services, _log } = this;
211212

213+
const current = _services.get(path);
214+
212215
// Need to specifically check for false because this value is undefined
213-
// when a machine first boots up.
214-
if(changed === false) {
216+
// when a machine first boots up. Also check number of times we've built this machine
217+
// and run anyways if we've never built it before
218+
if(changed === false && current.run > 0) {
215219
return;
216220
}
217221

218-
const { _services, _log } = this;
219-
220222
// Save off the state
221-
_services.get(path).state = state;
223+
current.state = state;
222224

223225
_log(`[${path}][_onState] checking children`);
224226

tests/invoked.test.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,4 +673,50 @@ describe("invoked machines", (it) => {
673673
}
674674
]`);
675675
});
676+
677+
it("should build a tree even when the child machine immediately fires a noop event", async () => {
678+
const childMachine = createMachine({
679+
initial : "one",
680+
681+
// This invoke always fires an event immediately, but it doesn't cause a transition
682+
// so when _onState in the component tree instance is triggered changed is set to false,
683+
// even though the tree for that service has never been built. Added a check to ignore
684+
// changed and build anyways if it's the first time the service has been seen.
685+
invoke : [{
686+
id : "invoke",
687+
src : () => (dispatch) => dispatch("ONE"),
688+
}],
689+
690+
states : {
691+
one : {
692+
meta : {
693+
component : component("child-one"),
694+
},
695+
},
696+
},
697+
});
698+
699+
const { tree } = await getTree({
700+
id : "parent",
701+
initial : "one",
702+
703+
states : {
704+
one : {
705+
invoke : {
706+
id : "child",
707+
src : childMachine,
708+
},
709+
},
710+
},
711+
});
712+
713+
snapshot(tree, `[
714+
[Object: null prototype] {
715+
path: "one",
716+
component: [Function: child-one],
717+
props: false,
718+
children: []
719+
}
720+
]`);
721+
});
676722
});

0 commit comments

Comments
 (0)