@supunlakmal/hooks
Version:
A collection of reusable React hooks
89 lines • 4.1 kB
JavaScript
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