react-next-keep-alive
Version:
Module for caching views in next.js
285 lines (278 loc) • 9.6 kB
JavaScript
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