UNPKG

@supunlakmal/hooks

Version:

A collection of reusable React hooks

89 lines 4.1 kB
import { useState, useCallback, useMemo } from 'react'; /** * Manages complex state using an explicit finite state machine definition. * Inspired by libraries like XState. * * @param config The state machine configuration object. * @returns An object containing the current state, context, and a send function. */ export const useFiniteStateMachine = (config) => { var _a; const [currentState, setCurrentState] = useState(config.initial); const [context, setContext] = useState((_a = config.context) !== null && _a !== void 0 ? _a : {}); const send = useCallback((eventInput) => { var _a, _b; const eventType = typeof eventInput === 'string' ? eventInput : eventInput.type; const eventPayload = typeof eventInput === 'object' ? eventInput.payload : undefined; const currentStateConfig = config.states[currentState]; if (!(currentStateConfig === null || currentStateConfig === void 0 ? void 0 : currentStateConfig.on) || !currentStateConfig.on[eventType]) { console.warn(`No transition defined for event '${eventType}' in state '${currentState}'`); return; // No transition defined for this event in the current state } const transitionConfig = currentStateConfig.on[eventType]; let targetState; let actions; let condition; if (typeof transitionConfig === 'string') { // Shorthand transition targetState = transitionConfig; } else { // Full transition object targetState = transitionConfig.target; actions = transitionConfig.actions; condition = transitionConfig.cond; } // Check condition if it exists if (condition && !condition(context, eventPayload)) { // console.log(`Condition not met for event '${eventType}' in state '${currentState}'`); return; // Condition not met, do not transition } // Execute actions and update context let nextContext = Object.assign({}, context); if (actions) { actions.forEach((action) => { const contextUpdate = action(nextContext, eventPayload); if (contextUpdate) { nextContext = Object.assign(Object.assign({}, nextContext), contextUpdate); } }); } // Execute exit actions for the current state const currentExitActions = (_a = config.states[currentState]) === null || _a === void 0 ? void 0 : _a.exit; if (currentExitActions) { currentExitActions.forEach((action) => { const contextUpdate = action(nextContext); if (contextUpdate) { nextContext = Object.assign(Object.assign({}, nextContext), contextUpdate); } }); } // Execute entry actions for the target state const targetEntryActions = (_b = config.states[targetState]) === null || _b === void 0 ? void 0 : _b.entry; if (targetEntryActions) { targetEntryActions.forEach((action) => { const contextUpdate = action(nextContext); if (contextUpdate) { nextContext = Object.assign(Object.assign({}, nextContext), contextUpdate); } }); } // Update context state setContext(nextContext); // Update current state setCurrentState(targetState); }, [currentState, context, config]); const matches = useCallback((state) => { return currentState === state; }, [currentState]); // Use useMemo to ensure the returned object reference is stable if state/context hasn't changed // This is a minor optimization for consumers memoizing based on the hook's return value const machineInstance = useMemo(() => ({ currentState, context, send, matches, }), [currentState, context, send, matches]); return machineInstance; }; //# sourceMappingURL=useFiniteStateMachine.js.map