A React KeepAlive component like keep-alive in vue
中文 | English
| Package | Version | Description |
|---|---|---|
| keepalive-for-react | Core keepalive functionality | |
| keepalive-for-react-router | React Router integration |
- Support React Router v6, v7, and v8
- 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)
-
Version Compatibility:
- For React 18, please use
keepalive-for-react@4.x.x - For React 19.2+, please use
keepalive-for-react@6
- 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.
-
Router integration supports
react-router-dom@6andreact-router@7/react-router@8.
npm install keepalive-for-reactyarn add keepalive-for-reactpnpm add keepalive-for-react- Install the packages for your React Router version.
keepalive-for-react-router@6supports all three versions.
# v6
npm install react-router-dom@6 keepalive-for-react@6 keepalive-for-react-router@6
# v7
npm install react-router@7 keepalive-for-react@6 keepalive-for-react-router@6
# v8
npm install react-router@^8.3.1 keepalive-for-react@6 keepalive-for-react-router@6React Router v8.3.1 requires Node.js >=22.22.0 and React / React DOM >=19.2.7. Update these dependencies before using v8.
- use KeepAlive in your project
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 / v8
// 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
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
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.2+
* @default false
* 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;
}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.
useEffectOnActive(() => {
console.log("active");
}, []);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 renderRun 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");
};
});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)");
};
});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 nodestype definition
interface KeepAliveRef {
refresh: (cacheKey?: string) => void;
destroy: (cacheKey?: string | string[]) => Promise<void>;
destroyAll: () => Promise<void>;
destroyOther: (cacheKey?: string) => Promise<void>;
getCacheNodes: () => Array<CacheNode>;
onCacheNodesChange: (callback: (cacheNodes: Array<CacheNode>) => void) => () => void;
}onCacheNodesChange subscribes to cache node additions, updates, refreshes, and removals, and returns an unsubscribe function. Multiple listeners are supported and notifications are scoped to each KeepAlive instance. Subscribing does not immediately invoke the callback; call getCacheNodes() if you need a snapshot of existing cache nodes. Subscribe in an effect and return the cleanup function:
useEffect(() => {
return aliveRef.current?.onCacheNodesChange(nodes => {
console.log(nodes);
});
}, [aliveRef]);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} />;
}install dependencies
pnpm installbuild package
pnpm build