packages/core/src/features/machines/hook.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import { GLOBAL_STATE } from '../../lib/global-state.js';
2 import type { PibblComponentRefs, PibblInstance } from '../../lib/types.js';
3 import { requireHookContext, useHookSlot } from '../../lib/hooks/hook-slot.js';
4 import { createMachine } from './runtime.js';
5 import type {
6 MachineActor,
7 MachineDefinition,
8 MachineEvent,
9 } from './types.js';
10
11 interface MachineHookSlot<Context, Event extends MachineEvent, Input, State extends string> {
12 readonly actor: MachineActor<Context, Event, Input, State>;
13 readonly componentRefs: PibblComponentRefs<unknown>;
14 readonly pibblInstance: PibblInstance;
15 started: boolean;
16 }
17
18 function isCurrentSlot<Context, Event extends MachineEvent, Input, State extends string>(
19 slot: MachineHookSlot<Context, Event, Input, State>,
20 ): boolean {
21 return !slot.componentRefs.teardowns.closed &&
22 !slot.pibblInstance.mainTeardowns.closed &&
23 GLOBAL_STATE.pibblInstances.get(slot.pibblInstance.canvas) === slot.pibblInstance &&
24 slot.pibblInstance.refs.get(slot.componentRefs.id) === slot.componentRefs;
25 }
26
27 /**
28 * Creates one component-owned state-machine actor for a hook slot.
29 *
30 * The actor starts only after its mounting render commits successfully and stops
31 * when the component unmounts. Its input is captured on the initial mount;
32 * changing an input object on a later render does not recreate or restart the
33 * actor. Use machine events for application state changes.
34 *
35 * @typeParam Context - Immutable machine context carried by each snapshot.
36 * @typeParam Event - Events accepted by the machine actor.
37 * @typeParam Input - Immutable input captured when this hook slot mounts.
38 * @param definition - Immutable machine behavior shared by all actor instances.
39 * @param options - Input used to initialize this mounted actor.
40 * @returns A stable actor with a readonly reactive snapshot.
41 *
42 * @example
43 * ```tsx
44 * const actor = useMachine(menuMachine, { input: { initialOpen: false } });
45 * const open = () => actor.send({ type: 'OPEN' });
46 * return <Text>{actor.snapshot.get().value}</Text>;
47 * ```
48 *
49 * @see {@link createMachine}
50 * @see {@link MachineActor}
51 */
52 export function useMachine<Context, Event extends MachineEvent, Input, State extends string>(
53 definition: MachineDefinition<Context, Event, Input, State>,
54 options: { readonly input: Input },
55 ): MachineActor<Context, Event, Input, State> {
56 const { componentRefs, pibblInstance } = requireHookContext();
57
58 const slot = useHookSlot<MachineHookSlot<Context, Event, Input, State>>(
59 'machine',
60 teardowns => {
61 const actor = createMachine(definition, options);
62 const value: MachineHookSlot<Context, Event, Input, State> = {
63 actor,
64 componentRefs,
65 pibblInstance,
66 started: false,
67 };
68 teardowns.add(() => actor.stop());
69 return value;
70 },
71 ).value;
72
73 const transaction = GLOBAL_STATE.renderTransaction;
74 if (!transaction) {
75 throw new Error('useMachine requires an active Pibbl render transaction.');
76 }
77 transaction.onFinish(success => {
78 if (!success || slot.started || !isCurrentSlot(slot)) return;
79 slot.started = true;
80 slot.actor.start();
81 });
82 return slot.actor;
83 }
84
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.