UNPKG

react-next-keep-alive

Version:
285 lines (278 loc) 9.6 kB
import React, { cloneElement, useRef, useState, memo, useEffect, Fragment } from 'react'; function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } const defaultEnabled = true; const KeepAliveProvider = props => { var _componentData$type; const { children, router } = props; const pageProps = children == null ? void 0 : children.props; const componentData = cloneElement(children); const CurrentComponent = componentData == null ? void 0 : componentData.type; const keepAliveCache = useRef({}); const [, forceUpdate] = useState(); const { name: keepAliveName, keepScrollEnabled, applyNewProps // @ts-ignore } = (componentData == null || (_componentData$type = componentData.type) == null ? void 0 : _componentData$type.keepAlive) || {}; // KeepAlive name const name = typeof keepAliveName === 'function' ? keepAliveName({ props: pageProps, router }) : keepAliveName; const isEnabled = () => { var _keepAliveCache$curre; return keepAliveCache == null || (_keepAliveCache$curre = keepAliveCache.current) == null || (_keepAliveCache$curre = _keepAliveCache$curre[name]) == null ? void 0 : _keepAliveCache$curre.enabled; }; const isKeptAlive = !!name; const keepScroll = !!keepScrollEnabled; // Add Component to retainedComponents if we haven't got it already if (isKeptAlive && !keepAliveCache.current[name]) { const Component = componentData == null ? void 0 : componentData.type; const MemoComponent = memo(Component); keepAliveCache.current[name] = { Component: MemoComponent, pageProps, scrollPos: 0, name, enabled: defaultEnabled }; } // Save the scroll position of current page before leaving const handleRouteChangeStart = () => { var _keepAliveCache$curre2; if (isKeptAlive && keepAliveCache != null && (_keepAliveCache$curre2 = keepAliveCache.current) != null && _keepAliveCache$curre2[name]) { keepAliveCache.current[name].scrollPos = window.scrollY; } }; // Restore the scroll position of cached page const handleRouteChangeComplete = () => { if (isKeptAlive && isEnabled() && keepScroll) { var _keepAliveCache$curre3; window.scrollTo(0, ((_keepAliveCache$curre3 = keepAliveCache.current[name]) == null ? void 0 : _keepAliveCache$curre3.scrollPos) || 0); // Just in case try again in next event loop setTimeout(() => { var _keepAliveCache$curre4; window.scrollTo(0, ((_keepAliveCache$curre4 = keepAliveCache.current[name]) == null ? void 0 : _keepAliveCache$curre4.scrollPos) || 0); }, 0); } }; // Enable/disable loading from cache const handleLoadFromCache = event => { const { name: controlsName, enabled: controlsEnabled } = (event == null ? void 0 : event.detail) || {}; if (keepAliveCache.current[controlsName]) { keepAliveCache.current[controlsName].enabled = controlsEnabled; } }; // Drop cache const handleDropCache = event => { const { name: dropKeepAliveName, scrollToTop } = (event == null ? void 0 : event.detail) || {}; // If no name, drop all cache if (!dropKeepAliveName) { keepAliveCache.current = {}; } else if (typeof dropKeepAliveName === 'string') { var _keepAliveCache$curre5; (_keepAliveCache$curre5 = keepAliveCache.current) == null || delete _keepAliveCache$curre5[dropKeepAliveName]; } else if (typeof dropKeepAliveName === 'function') { var _cachesToRemove$filte; const caches = dropKeepAliveName == null ? void 0 : dropKeepAliveName(Object.keys(keepAliveCache.current)); const cachesToRemove = Array.isArray(caches) ? caches : [caches]; // eslint-disable-next-line no-unused-expressions cachesToRemove == null || (_cachesToRemove$filte = cachesToRemove.filter(exists => exists)) == null || _cachesToRemove$filte.forEach(cacheName => { var _keepAliveCache$curre6; return (_keepAliveCache$curre6 = keepAliveCache.current) == null || delete _keepAliveCache$curre6[cacheName]; }); } if (scrollToTop && typeof window !== 'undefined') { window.scrollTo(0, 0); } forceUpdate({}); }; // Handle scroll position caching - requires an up-to-date router.asPath useEffect(() => { router.events.on('routeChangeStart', handleRouteChangeStart); router.events.on('routeChangeComplete', handleRouteChangeComplete); return () => { router.events.off('routeChangeStart', handleRouteChangeStart); router.events.off('routeChangeComplete', handleRouteChangeComplete); }; }, [router.asPath]); // Emit mounting events // @ts-ignore useEffect(() => { if (isKeptAlive) { window.dispatchEvent(new CustomEvent('onKeepAliveMount', { detail: name })); return () => { window.dispatchEvent(new CustomEvent('onKeepAliveUnmount', { detail: name })); }; } }, [CurrentComponent, pageProps]); /** * Listen to changes (enabled/disabled) */ useEffect(() => { window.addEventListener('keepAliveControls_LoadFromCache', handleLoadFromCache); window.addEventListener('keepAliveControls_DropCache', handleDropCache); return () => { window.removeEventListener('keepAliveControls_LoadFromCache', handleLoadFromCache); window.removeEventListener('keepAliveControls_DropCache', handleDropCache); }; }, []); const getCachedViewProps = cachedProps => { // Apply new props if (applyNewProps === true) { return pageProps; } // Apply combination of old and new props if (typeof applyNewProps === 'function') { return applyNewProps(cachedProps, pageProps); } // Apply cached props return cachedProps; }; /** * Custom useEffect which runs only when component alive. */ const getKeepAliveEffect = isHidden => { const useKeepAliveEffect = (effect, deps) => useEffect(() => { if (!isHidden) { return effect(); } }, deps); return useKeepAliveEffect; }; return ( // eslint-disable-next-line react/jsx-fragments React.createElement(Fragment, null, (!isKeptAlive || !isEnabled()) && children, React.createElement("div", { style: { display: isKeptAlive && isEnabled() ? 'block' : 'none' }, id: "keep-alive-container", "data-keepalivecontainer": true }, Object.entries(keepAliveCache.current).map(([cacheName, { Component, pageProps: cachedProps }]) => React.createElement("div", { key: cacheName, style: { display: name === cacheName ? 'block' : 'none' }, "data-keepalive": cacheName, "data-keepalive-hidden": name !== cacheName }, React.createElement(Component, _extends({ isHiddenByKeepAlive: name !== cacheName, useEffect: getKeepAliveEffect(name !== cacheName) }, getCachedViewProps(cachedProps))))))) ); }; const defaultOpts = { keepScrollEnabled: true, applyNewProps: false }; const withKeepAlive = (Component, name, opts = defaultOpts) => { const KeepAlive = props => // eslint-disable-next-line react/jsx-fragments React.createElement(Fragment, null, React.createElement(Component, _extends({}, props))); // Copy getInitial props so it will run as well // @ts-ignore if (Component.getInitialProps) { // @ts-ignore KeepAlive.getInitialProps = Component.getInitialProps; } const { keepScrollEnabled, applyNewProps } = opts || {}; KeepAlive.keepAlive = { name, keepScrollEnabled, applyNewProps }; return KeepAlive; }; /** * Hook for handling mount event * @param name * @param effect */ const useKeepAliveMountEffect = (name, effect) => { // Trigger mount const handleMountedEvent = e => { if ((e == null ? void 0 : e.detail) === name && typeof effect === 'function') { effect(); } }; useEffect(() => { window.addEventListener('onKeepAliveMount', handleMountedEvent); return () => { window.removeEventListener('onKeepAliveMount', handleMountedEvent); }; }, []); }; /** * Hook for handling unmount event * @param name * @param effect */ const useKeepAliveUnmountEffect = (name, effect) => { // Trigger mount const handleUnmountedEvent = e => { if ((e == null ? void 0 : e.detail) === name && typeof effect === 'function') { effect(); } }; useEffect(() => { window.addEventListener('onKeepAliveUnmount', handleUnmountedEvent); return () => { window.removeEventListener('onKeepAliveUnmount', handleUnmountedEvent); }; }, []); }; function keepAliveLoadFromCache(name, enabled) { if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('keepAliveControls_LoadFromCache', { detail: { name, enabled } })); } } function keepAliveDropCache(name, scrollToTop) { if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('keepAliveControls_DropCache', { detail: { name, scrollToTop } })); } } export { KeepAliveProvider, keepAliveDropCache, keepAliveLoadFromCache, useKeepAliveMountEffect, useKeepAliveUnmountEffect, withKeepAlive }; //# sourceMappingURL=index.modern.mjs.map