UNPKG

envoc-hook-local-slice

Version:

envoc-table

129 lines (128 loc) 6.39 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.useLocalSlice = void 0; var react_1 = require("react"); var immer_1 = __importDefault(require("immer")); function useLocalSlice(_a) { var initialState = _a.initialState, reducers = _a.reducers, _b = _a.slice, slice = _b === void 0 ? 'unnamed' : _b; (0, react_1.useDebugValue)(slice); var reducerKeys = Object.keys(reducers); var stateRef = (0, react_1.useRef)(initialState); var currentDraftRef = (0, react_1.useRef)(); var reducersRef = (0, react_1.useRef)(reducers); reducersRef.current = reducers; var abortControllersRef = (0, react_1.useRef)([]); var _c = (0, react_1.useState)(initialState), visibleState = _c[0], setVisibleState = _c[1]; (0, react_1.useEffect)(function () { // trigger all aborts due to an unmount var ref = abortControllersRef; return function cleanupAborts() { var abortControllers = ref.current; for (var i = 0; i < abortControllers.length; i++) { var abortController = abortControllers[i]; abortController.abort(); } }; }, []); var dispatchAction = (0, react_1.useMemo)(function () { // a map of actions available. // should be one method per provided reducer name var map = {}; var _loop_1 = function (actionType) { map[actionType] = function (payload) { // each time an action is called with some payload the correct method should be called with the current state // this includes ensuring that if a reducer calls another action that the action called has the correct draft state handledDispatchedAction(actionType, reducersRef, payload, stateRef, currentDraftRef, abortControllersRef, setVisibleState); }; }; // each reducer key is also the name of an action for (var _i = 0, reducerKeys_1 = reducerKeys; _i < reducerKeys_1.length; _i++) { var actionType = reducerKeys_1[_i]; _loop_1(actionType); } return map; }, // we only want new actions if the keys of the reducers change (which should be never to be honest) // eslint-disable-next-line react-hooks/exhaustive-deps [JSON.stringify(reducerKeys)]); return [visibleState, dispatchAction]; } exports.useLocalSlice = useLocalSlice; // handles each call to an action method in a consistent way function handledDispatchedAction(actionType, reducersRef, payload, stateRef, currentDraftRef, abortControllersRef, setVisibleState) { // this is the actual reducer - it could be a direct function or an object (checked by typescript) var reducers = reducersRef.current; var reducerDef = reducers[actionType]; if (typeof reducerDef === 'function') { // standard reducer function callFunctionWithDraft(function (draft) { return reducerDef(draft, payload); }); } else { // action with a promise - so let's make sure we can cancel var controller_1 = new AbortController(); abortControllersRef.current.push(controller_1); callFunctionWithDraft(function (draft) { // pending method generate the promise var promise = reducerDef.pending(draft, payload, controller_1.signal); if (reducerDef.resolved) { // only try 'resolved' if provided var resolvedBlock_1 = reducerDef.resolved; promise = promise.then(function (x) { if (controller_1.signal.aborted) { // never call anything if we have aborted return; } callFunctionWithDraft(function (draft) { return resolvedBlock_1(draft, payload, x); }); }); } var rejectedBlock = reducerDef.rejected; // we always handle the catch to avoid needless cancel errors in some cases // TODO: thoughts? promise = promise.catch(function (e) { if (controller_1.signal.aborted) { return; } if (!rejectedBlock) { throw e; } callFunctionWithDraft(function (draft) { return rejectedBlock(draft, payload, e); }); }); var finallyBlock = reducerDef.finally; promise.finally(function () { if (controller_1.signal.aborted) { return; } abortControllersRef.current = abortControllersRef.current.filter(function (x) { return x !== controller_1; }); if (!finallyBlock) { return; } callFunctionWithDraft(function (draft) { return finallyBlock(draft, payload); }); }); }); } // calls to adjust the state through callback should be made consistent // Note especially the usage of currentDraftRef function callFunctionWithDraft(callback) { if (currentDraftRef.current) { //there is already a draft being built - keep using that. // e.g. a reducer calling another action before returning will hit this // failure to call with the ongoing draft results in inconsistent behavior callback(currentDraftRef.current); } else { // this is the initial call to the actions, we will produce the draft stateRef.current = (0, immer_1.default)(stateRef.current, function (draftState) { currentDraftRef.current = draftState; // triggering the callback may result in other actions being called // so we set the current working draft ref first callback(currentDraftRef.current); }); // as soon as the state is updated clear our working copy and update the visible state currentDraftRef.current = undefined; setVisibleState(stateRef.current); } } }