Last active
September 27, 2023 15:22
-
-
Save cevr/0632f38870fb9d239385d84a0d297aef to your computer and use it in GitHub Desktop.
Accessing Shared Child of Child Actor using xstate & @xstate/react (v5 beta)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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