irychen / keepalive-for-react

React KeepAlive is a component that can cache the state of the component and reuse it when needed.
MIT License
193 stars 34 forks source link
keepalive react react-keepalive

keepalive-for-react logo

KeepAlive for React

A React KeepAlive component like keep-alive in vue

中文 | English

NPM version NPM downloads

Features

Attention

Install

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

Usage

in react-router-dom v6+

  1. install react-router-dom v6+
npm install react-router-dom keepalive-for-react
  1. use KeepAlive in your project
import { KeepAliveRouteOutlet } from "keepalive-for-react";

function Layout() {
    return (
        <div className="layout">
            <KeepAliveRouteOutlet />
        </div>
    );
}

or

import { useMemo } from "react";
import { useLocation } from "react-router-dom";
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;
    /**
     * transition duration default 200
     */
    duration?: number;
    aliveRef?: RefObject<KeepAliveRef | undefined>;
}

Hooks

useEffectOnActive

useEffectOnActive(() => {
    console.log("active");
}, []);

useLayoutEffectOnActive

useLayoutEffectOnActive(
    () => {
        console.log("active");
    },
    [],
    false,
);
// the third parameter is optional, default is true,
// which means the callback will be skipped when the useLayoutEffect is triggered in first render

useKeepAliveContext

type definition

interface KeepAliveContext {
    /**
     * whether the component is active
     */
    active: boolean;
    /**
     * 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, current active cached 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, destroy all components except the current active cached component.
     */
    destroyOther: (cacheKey?: string) => Promise<void>;
    /**
     * get the cache nodes
     */
    getCacheNodes: () => Array<CacheNode>;
}
const { active, refresh, destroy, getCacheNodes } = useKeepAliveContext();
// 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>;
}
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

link package to global

pnpm link --global

test in demo project

cd demo
pnpm link --global keepalive-for-react