Skip to content

packages/core/src/features/machines/hook.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

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 built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.