import { type RefObject, useEffect, useRef } from 'react'; import isEqual from '@gilbarbara/deep-equal'; import is from 'is-lite'; import type { EmitEvent } from '~/hooks/useEventEmitter'; import type { MergedProps } from '~/hooks/useTourEngine'; import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '~/literals'; import { treeChanges } from '~/modules/changes'; import { log } from '~/modules/helpers'; import { validateSteps } from '~/modules/step'; import createStore from '~/modules/store'; import type { StoreState } from '~/modules/store'; import type { Actions, Controls, Status, StepMerged } from '~/types'; interface UsePropSyncParams { controls: Controls; emitEvent: EmitEvent; props: MergedProps; state: StoreState; store: RefObject>; } export default function usePropSync({ controls, emitEvent, props, state, store, }: UsePropSyncParams): void { const { debug, initialStepIndex, run, stepIndex, steps } = props; const previousPropsRef = useRef(undefined); const stateRef = useRef(state); const controlsRef = useRef(controls); stateRef.current = state; controlsRef.current = controls; useEffect(() => { const previousProps = previousPropsRef.current; previousPropsRef.current = props; if (!previousProps || props === previousProps) { return; } const { hasChanged } = treeChanges(props, previousProps); if (!isEqual(previousProps.steps, steps)) { if (validateSteps(steps, debug)) { store.current.setSteps(steps); } else { log(debug, 'tour', 'Steps are not valid', steps); emitEvent(EVENTS.ERROR, (steps[0] ?? { target: '', content: '' }) as StepMerged, { error: new Error('Steps are not valid'), }); } } if (hasChanged('run')) { if (run) { if (store.current.getState().size) { controlsRef.current.start(stepIndex ?? initialStepIndex); } } else { controlsRef.current.stop(); } } else if (is.number(stepIndex) && hasChanged('stepIndex')) { const nextAction: Actions = is.number(previousProps.stepIndex) && previousProps.stepIndex < stepIndex ? ACTIONS.NEXT : ACTIONS.PREV; if (!([STATUS.FINISHED, STATUS.SKIPPED] as Array).includes(stateRef.current.status)) { store.current.updateState( { action: nextAction, index: stepIndex, lifecycle: LIFECYCLE.INIT, positioned: false }, true, ); } } }, [debug, emitEvent, initialStepIndex, props, run, stepIndex, steps, store]); }