Skip to content

About

React KeepAlive is a component that can cache the state of the component and reuse it when needed.

Topics

Resources

Stars

340 stars

Watchers

6 watching

Forks

Repository files navigation

keepalive-for-react logo

KeepAlive for React

A React KeepAlive component like keep-alive in vue

中文 | English

NPM version NPM downloads

Packages

Package Version Description
keepalive-for-react NPM version Core keepalive functionality
keepalive-for-react-router NPM version React Router integration

Features

  • 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 active and inactive
  • 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 keepalive-for-react@4.x.x
    • For React 19.2+, please use keepalive-for-react@6
  • 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@6 and react-router@7 / react-router@8.

Install

npm install keepalive-for-react
yarn add keepalive-for-react
pnpm add keepalive-for-react

Usage

With React Router v6, v7, or v8

  1. Install the packages for your React Router version. keepalive-for-react-router@6 supports 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@6

React Router v8.3.1 requires Node.js >=22.22.0 and React / React DOM >=19.2.7. Update these dependencies before using v8.

  1. 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

Open in StackBlitz

in simple tabs

npm install keepalive-for-react
const 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

Open in StackBlitz

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.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;
}

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 render

useEffectOnCreate

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 nodes

useKeepAliveRef

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>;
    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} />;
}

Development

install dependencies

pnpm install

build package

pnpm build

About

React KeepAlive is a component that can cache the state of the component and reuse it when needed.

Topics

Resources

Stars

340 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages