UNPKG

bgio-effects

Version:

📤 Helpers for managing state effects in boardgame.io

79 lines (78 loc) • 3.72 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useEffectListener = void 0; const react_1 = require("react"); const contexts_1 = require("../contexts"); const utils_1 = require("./utils"); /** * No-op fallback for `useCallback` that is never actually called. */ // istanbul ignore next function noop() { } /** * Subscribe to a Mitt instance with automatic callback memoization & clean-up. * @param emitter - The `EffectsEmitter` instance to subscribe to. * @param effectType - Name of the effect to listen for. '*' listens to any. * @param startHandler - Function to call when the event is emitted. * @param startDeps - Array of variables the handler depends on. */ function useEmitterSubscription(emitter, effectType, startHandler, startDeps, endHandler, endDeps = []) { endHandler = endHandler || noop; /** * This is not strictly speaking a safe use of `useCallback.` * Code like `useEffectListener('x', flag ? () => {} : () => {}, [])` * will be buggy. The initially passed function will never be updated because * the functions themselves aren’t included as dependencies (to avoid * infinite loops). It seems there is no technically correct way to * wrap `useCallback` in a custom hook if the function comes from outside * the hook. The only 100% correct solution here would be to require users * to pass a stable function they got from `useCallback` themselves, * which for now we’ve avoided in order to simplify the API. */ // eslint-disable-next-line react-hooks/exhaustive-deps const onStartMemo = (0, react_1.useCallback)(startHandler, [...startDeps, effectType]); // eslint-disable-next-line react-hooks/exhaustive-deps const onEndMemo = (0, react_1.useCallback)(endHandler, [...endDeps, effectType]); (0, react_1.useEffect)(() => { let cleanup; const onStart = (...args) => { if (typeof cleanup === 'function') cleanup(); cleanup = onStartMemo(...args); }; let onEndCleanup; const onEnd = (...args) => { if (typeof onEndCleanup === 'function') onEndCleanup(); onEndCleanup = onEndMemo(...args); }; const unsubscribe = emitter.on(effectType, onStart, onEnd); return () => { unsubscribe(); if (typeof cleanup === 'function') cleanup(); if (typeof onEndCleanup === 'function') onEndCleanup(); }; }, [effectType, emitter, onStartMemo, onEndMemo]); } /** * Subscribe to events emitted by the effects state. * @param effectType - Name of the effect to listen for. '*' listens to any. * @param callback - Function to call when the event is emitted. * @param dependencyArray - Array of variables the callback function depends on. * @param onEndCallback - Function to call when the effect ends. * @param onEndDependencyArray - Array of variables onEndCallback depends on. */ function useEffectListener(...args) { const emitter = (0, react_1.useContext)(contexts_1.EffectsContext); const [effectType, cb, deps, onEndCb, onEndDeps] = args; if (!emitter) throw new Error((0, utils_1.hookErrorMessage)('useEffectListener')); if (!deps) throw new TypeError('useEffectListener must receive a dependency list as its third argument.'); if (onEndCb && !onEndDeps) throw new TypeError('useEffectListener must receive a dependency list as its fifth argument when using an onEffectEnd callback.'); useEmitterSubscription(emitter, effectType, cb, deps, onEndCb, onEndDeps); } exports.useEffectListener = useEffectListener;