Skip to content

Commit

Permalink
Fixed an issue with initial state of invoked machines being read with…
Browse files Browse the repository at this point in the history
…out custom data
  • Loading branch information
Andarist committed Jul 5, 2020
1 parent 8226700 commit 3ab3f25
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-apples-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed an issue with initial state of invoked machines being read without custom data passed to them which could lead to a crash when evaluating transient transitions for the initial state.
16 changes: 12 additions & 4 deletions packages/core/src/Actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import {
EventObject,
Subscribable,
InvokeDefinition,
AnyEventObject
AnyEventObject,
SCXML
} from './types';
import { StateMachine } from '.';
import { isMachine } from './utils';
import { isMachine, mapContext } from './utils';

export interface Actor<
TContext = any,
Expand Down Expand Up @@ -43,14 +44,21 @@ export function createNullActor(id: string): Actor {
*/
export function createInvocableActor<TC, TE extends EventObject>(
invokeDefinition: InvokeDefinition<TC, TE>,
machine: StateMachine<TC, any, TE>
machine: StateMachine<TC, any, TE>,
context: TC,
_event: SCXML.Event<TE>
): Actor {
const tempActor = createNullActor(invokeDefinition.id);
const serviceCreator = machine.options.services?.[invokeDefinition.src];
tempActor.deferred = true;

if (isMachine(serviceCreator)) {
tempActor.state = serviceCreator.initialState;
tempActor.state = serviceCreator.getInitialState(
serviceCreator.initialStateValue!,
invokeDefinition.data
? mapContext(invokeDefinition.data, context, _event)
: undefined
);
}

tempActor.meta = invokeDefinition;
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/StateNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,9 @@ class StateNode<
(acc, action) => {
acc[action.activity.id] = createInvocableActor(
action.activity,
this.machine
this.machine,
updatedContext,
_event
);

return acc;
Expand Down Expand Up @@ -1446,7 +1448,7 @@ class StateNode<

return toStatePath(stateIdentifier, this.delimiter);
}
private get initialStateValue(): StateValue | undefined {
public get initialStateValue(): StateValue | undefined {
if (this.__cache.initialStateValue) {
return this.__cache.initialStateValue;
}
Expand Down
41 changes: 41 additions & 0 deletions packages/core/test/transient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,4 +629,45 @@ describe('transient states (eventless transitions)', () => {

service.send('ADD');
});

it("shouldn't crash when invoking a machine with initial transient transition depending on custom data", () => {
const timerMachine = Machine({
initial: 'intitial',
states: {
intitial: {
always: [
{
target: `finished`,
cond: (ctx) => ctx.duration < 1000
},
{
target: `active`
}
]
},
active: {},
finished: { type: 'final' }
}
});

const machine = Machine({
initial: 'active',
context: {
customDuration: 3000
},
states: {
active: {
invoke: {
src: timerMachine,
data: {
duration: (context) => context.customDuration
}
}
}
}
});

const service = interpret(machine);
expect(() => service.start()).not.toThrow();
});
});
45 changes: 44 additions & 1 deletion packages/xstate-react/test/useMachine.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { useMachine, useService } from '../src';
import { useMachine, useService, useActor } from '../src';
import {
Machine,
assign,
Expand Down Expand Up @@ -604,4 +604,47 @@ describe('useMachine (strict mode)', () => {
</React.StrictMode>
);
});

it('custom data should be available right away for the invoked actor', (done) => {
const childMachine = Machine({
initial: 'intitial',
context: {
value: 100
},
states: {
intitial: {}
}
});

const machine = Machine({
initial: 'active',
states: {
active: {
invoke: {
id: 'test',
src: childMachine,
data: {
value: () => 42
}
}
}
}
});

const Test = () => {
const [state] = useMachine(machine);
const [childState] = useActor(state.children.test);

expect(childState.context.value).toBe(42);

return null;
};

render(
<React.StrictMode>
<Test />
</React.StrictMode>
);
done();
});
});

0 comments on commit 3ab3f25

Please sign in to comment.