Skip to content

Instantly share code, notes, and snippets.

@cevr
Last active September 27, 2023 15:22
Show Gist options
  • Save cevr/0632f38870fb9d239385d84a0d297aef to your computer and use it in GitHub Desktop.
Save cevr/0632f38870fb9d239385d84a0d297aef to your computer and use it in GitHub Desktop.
Accessing Shared Child of Child Actor using xstate & @xstate/react (v5 beta)
import type { ActorRefFrom, AnyActorRef, AnyStateMachine, SnapshotFrom } from 'xstate';
import { useMachine, useSelector } from '@xstate/react';
type MachineStateFrom<T extends AnyStateMachine | AnyActorRef> = [
state: SnapshotFrom<T>,
send: ActorRefFrom<T>['send'],
actor: ActorRefFrom<T>,
];
export function useActorFromRef<T extends AnyActorRef>(ref: T): MachineStateFrom<T> {
const state = useActorSnapshot(ref);
return [state, ref.send, ref] as MachineStateFrom<T>;
}
function identity<T>(x: T): T {
return x;
}
const useActorSnapshot = <T extends AnyActorRef>(ref: T): SnapshotFrom<T> => {
return useSelector(ref, identity);
};
export function useLoyaltyState(): MachineStateFrom<typeof loyaltyMachine> {
const [state] = useSessionState(); // this is `useMachine()` passed in context
// this pattern of using a ref to store the actors is necessary because
// when the state changes, the actors will be removed before react can
// unmount the commponent, so we need to store them in a ref to keep react from throwing errors in the tree (understandably)
// in practice, this happens so quickly that the user will never be able to continue
// interacting with the actors, so it's not a problem
const coverActor = React.useRef<ActorRefFrom<typeof coverMachine>>();
if (!coverActor.current) {
// the key in the `children` object comes from the `id` strinng passed into the invoke config
coverActor.current = state.children.cover as unknown as ActorRefFrom<typeof coverMachine>;
}
const checkoutActor = React.useRef<ActorRefFrom<typeof checkoutMachine>>();
if (!checkoutActor.current) {
checkoutActor.current = state.children.checkout as unknown as ActorRefFrom<
typeof checkoutMachine
>;
}
// however if it is still undefined at this point, its because we are not in the cover or checkout state
if (!coverActor.current && !checkoutActor.current) {
throw new Error('useLoyaltyState must be used during the `cover` or `checkout` session state');
}
const childState = useActorSnapshot(
coverActor.current ? coverActor.current : checkoutActor.current,
);
const loyaltyActor = React.useRef<ActorRefFrom<typeof loyaltyMachine>>();
if (!loyaltyActor.current) {
loyaltyActor.current = childState.children.loyalty as unknown as ActorRefFrom<
typeof loyaltyMachine
>;
}
if (!loyaltyActor.current) {
throw new Error(
'useLoyaltyState must be used during the `loyalty` state with the `checkout` or `cover` state',
);
}
return useActorFromRef(loyaltyActor.current);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment