keepalive-for-react
v6.0.0
Published
A react <KeepAlive/> component like <keep-alive/> in vue
Maintainers
Readme
中文 | English
Packages
| Package | Version | Description |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| keepalive-for-react | | Core keepalive functionality |
| keepalive-for-react-router |
| React Router integration |
Features
- Support react-router-dom v6+ or react-router v7+
- Support React v16+ ~ v18+ (v19.2 Activity component support [v5.0.0])
- Support Suspense and Lazy import
- Support ErrorBoundary
- Support Custom Container
- Support Switching Animation Transition with className
activeandinactive - Simply implement, without any extra dependencies and hacking ways
- Only 6KB minified size
- Support interrupt state effect when component is not active (v5.0.0)
Attention
Version Compatibility:
- For React 18, please use
[email protected] - For React 19.2+, please use
[email protected]
- For React 18, please use
DO NOT use <React.StrictMode />, it CANNOT work with keepalive-for-react in development mode. because it can lead to some unexpected behavior.
In Router only support react-router-dom v6+
Install
npm install keepalive-for-reactyarn add keepalive-for-reactpnpm add keepalive-for-reactUsage
in react-router-dom v6+ or react-router v7+
- install react-router-dom v6+ or react-router v7+
# v6+
npm install react-router-dom keepalive-for-react [email protected]
# v7+
npm install react-router keepalive-for-react [email protected]- use KeepAlive in your project
// v6+ [email protected]
// v7+ [email protected]
import KeepAliveRouteOutlet from "keepalive-for-react-router";
function Layout() {
return (
<div className="layout">
<KeepAliveRouteOutlet />
</div>
);
}or
import { useMemo } from "react";
// v6+
import { useLocation, useOutlet } from "react-router-dom";
// v7
// import { useLocation, useOutlet } from "react-router";
import { KeepAlive, useKeepAliveRef } from "keepalive-for-react";
function Layout() {
const location = useLocation();
const aliveRef = useKeepAliveRef();
const outlet = useOutlet();
// determine which route component to is active
const currentCacheKey = useMemo(() => {
return location.pathname + location.search;
}, [location.pathname, location.search]);
return (
<div className="layout">
<MemoizedScrollTop>
<KeepAlive transition aliveRef={aliveRef} activeCacheKey={currentCacheKey} max={18}>
<Suspense fallback={<LoadingArea />}>
<SpreadArea>{outlet}</SpreadArea>
</Suspense>
</KeepAlive>
</MemoizedScrollTop>
</div>
);
}details see examples/react-router-dom-simple-starter
in simple tabs
npm install keepalive-for-reactconst tabs = [
{
key: "tab1",
label: "Tab 1",
component: Tab1,
},
{
key: "tab2",
label: "Tab 2",
component: Tab2,
},
{
key: "tab3",
label: "Tab 3",
component: Tab3,
},
];
function App() {
const [currentTab, setCurrentTab] = useState<string>("tab1");
const tab = useMemo(() => {
return tabs.find(tab => tab.key === currentTab);
}, [currentTab]);
return (
<div>
{/* ... */}
<KeepAlive transition={true} activeCacheKey={currentTab} exclude={["tab3"]}>
{tab && <tab.component />}
</KeepAlive>
</div>
);
}details see examples/simple-tabs-starter
KeepAlive Props
type definition
interface KeepAliveProps {
// determine which component to is active
activeCacheKey: string;
children?: KeepAliveChildren;
/**
* max cache count default 10
*/
max?: number;
exclude?: Array<string | RegExp> | string | RegExp;
include?: Array<string | RegExp> | string | RegExp;
onBeforeActive?: (activeCacheKey: string) => void;
customContainerRef?: RefObject<HTMLDivElement>;
cacheNodeClassName?: string;
containerClassName?: string;
errorElement?: ComponentType<{
children: ReactNode;
}>;
/**
* transition default false
*/
transition?: boolean;
/**
* use view transition to animate the component when switching tabs
* @see https://developer.chrome.com/docs/web-platform/view-transitions/
*/
viewTransition?: boolean;
/**
* transition duration default 200
*/
duration?: number;
aliveRef?: RefObject<KeepAliveRef | undefined | null>;
/**
* max alive time for cache node (second)
* @default 0 (no limit)
*/
maxAliveTime?: number | MaxAliveConfig[];
/**
* enable Activity component from react 19+
* @default false
* Activity component can improve performance
* Attention: if enable Activity component, useEffect will trigger when the component is active
*/
enableActivity?: boolean;
customClassNames?: {
active?: string;
inactive?: string;
};
}
interface MaxAliveConfig {
match: string | RegExp;
expire: number;
}Custom state class names
customClassNames customizes the active and inactive class names on cache nodes. It defaults to { active: "active", inactive: "inactive" }; omitted fields keep their defaults. Each value must be a single non-empty CSS class name.
<KeepAlive activeCacheKey={currentTab} transition customClassNames={{ active: "tab-active", inactive: "tab-inactive" }}>
{children}
</KeepAlive>Update your transition CSS selectors to match the custom names. cacheNodeClassName sets the cache node's base class independently of these state classes.
Hooks
useEffectOnActive
useEffectOnActive(() => {
console.log("active");
}, []);useLayoutEffectOnActive
useLayoutEffectOnActive(
() => {
console.log("active");
},
[],
false,
);
// the third parameter is optional, default is false,
// if true, which means the callback will be skipped when the useLayoutEffect is triggered in first renderuseEffectOnCreate
Run a callback only once when the component is first created (cached), and run the returned cleanup only when the component is destroyed from the cache. Unlike useEffect(fn, []), it will NOT re-run when the cached component is re-activated.
useEffectOnCreate(() => {
console.log("component created");
return () => {
console.log("component destroyed");
};
});useLayoutEffectOnCreate
Same as useEffectOnCreate but uses useLayoutEffect internally. Useful when the create-time logic needs to run synchronously before the browser paints.
useLayoutEffectOnCreate(() => {
console.log("component created (layout)");
return () => {
console.log("component destroyed (layout)");
};
});useKeepAliveContext
type definition
interface KeepAliveContext {
/**
* whether the component is active
*/
active: boolean;
/**
* The key of this component's cache node, which may differ from the active node's key.
*/
cacheKey: string;
/**
* refresh the component
* @param {string} [cacheKey] - The cache key of the component. If not provided, the current cached component will be refreshed.
*/
refresh: (cacheKey?: string) => void;
/**
* destroy the component
* @param {string} [cacheKey] - the cache key of the component, if not provided, the cache node containing the calling component will be destroyed
*/
destroy: (cacheKey?: string | string[]) => Promise<void>;
/**
* destroy all components
*/
destroyAll: () => Promise<void>;
/**
* destroy other components except the provided cacheKey
* @param {string} [cacheKey] - The cache key of the component. If not provided, keep the cache node containing the calling component and destroy the others.
*/
destroyOther: (cacheKey?: string) => Promise<void>;
/**
* get the cache nodes
*/
getCacheNodes: () => Array<CacheNode>;
}const { active, cacheKey, refresh, destroy, getCacheNodes } = useKeepAliveContext();
// cacheKey identifies this component's cache node
// active is a boolean, true is active, false is inactive
// refresh is a function, you can call it to refresh the component
// destroy is a function, you can call it to destroy the component
// ...
// getCacheNodes is a function, you can call it to get the cache nodesuseKeepAliveRef
type definition
interface KeepAliveRef {
refresh: (cacheKey?: string) => void;
destroy: (cacheKey?: string | string[]) => Promise<void>;
destroyAll: () => Promise<void>;
destroyOther: (cacheKey?: string) => Promise<void>;
getCacheNodes: () => Array<CacheNode>;
}function App() {
const aliveRef = useKeepAliveRef();
// aliveRef.current is a KeepAliveRef object
// you can call refresh and destroy on aliveRef.current
aliveRef.current?.refresh();
// it is not necessary to call destroy manually, KeepAlive will handle it automatically
aliveRef.current?.destroy();
return <KeepAlive aliveRef={aliveRef}>{/* ... */}</KeepAlive>;
}
// or
function AppRouter() {
const aliveRef = useKeepAliveRef();
// aliveRef.current is a KeepAliveRef object
// you can call refresh and destroy on aliveRef.current
aliveRef.current?.refresh();
aliveRef.current?.destroy();
return <KeepAliveRouteOutlet aliveRef={aliveRef} />;
}Development
install dependencies
pnpm installbuild package
pnpm build