/react-fiber-keep-alive

A component that maintains component state and avoids repeated re-rendering.

Primary LanguageJavaScriptMIT LicenseMIT

Keep-Alive for React DOM

npm downloads typescript LICENSE

<KeepAlive> is a component that maintains component state and avoids repeated re-rendering.

✨ Features

  • Only based on React Fiber and React Hooks.
  • Triggers original class component life circle.
  • Triggers original effect hooks.
  • Supports context updates.
  • Supports multiple keep-alive.
  • Supports react-dom v16.8+.
  • Supports react-dom v17.
  • Supports react-dom v18.

📦 Installation

npm install --save react-fiber-keep-alive

🔨 Usage

import React from 'react';
import ReactDOM from 'react-dom';
import KeepAlive from 'react-fiber-keep-alive';

const root = document.getElementById('root');

ReactDOM.render((
    <KeepAlive.Provider value={root}>
        ...
        <KeepAlive name="test">
            <YourComponent />
        </KeepAlive>
        ...
    </KeepAlive.Provider>
), root);

📝 API

  • Provide root container element.

    <KeepAlive.Provider value={container}>
    • Must be the root container of render().
    • If not provided, keep-alive will be disabled.
  • Wrap your component with <KeepAlive>

    <KeepAlive name="unique-key">
        <YourComponent />
    </KeepAlive>
    • prop "name" is a required unique string used to identify the cache.
    • prop "ignore" is a optional boolean used to bypass and clear the cache. (since 0.5.0)
    • prop "onRead(name: string): void" is a optional callback after cache applied. (since 0.7.0)
    • prop "onSave(name: string): void" is a optional callback after cache saved. (since 0.7.0)
  • Wrap your component with keepLive().

    import { keepAlive } from 'react-fiber-keep-alive';
    
    const NewComponent = keepAlive(YourComponent, (props) => {
        // props: the income props for `<YourComponent>`
        
        // you can use react hooks here
    
        return `unique-key`;
    
        // or
    
        return {
            name: `unique-key`,
            // other props for `<KeepAlive>`
        };
    });
  • Hook: useIgnoreKeepAlive() returns a cache cleaner function.

    import { useIgnoreKeepAlive } from 'react-fiber-keep-alive';
    
    const ignoreCache = useIgnoreKeepAlive();
    
    ignoreCache(`unique-key`);
  • If the render() of class component has side effects.

    import { markClassComponentHasSideEffectRender } from 'react-fiber-keep-alive';
    
    markClassComponentHasSideEffectRender(ClassComponent);
    
    // Example:
    class Test extends React.Component {
        render() {
            // side effect here, ex: emit event here.
            return null;
        }
    }
    markClassComponentHasSideEffectRender(Test);
  • If no need to trigger the effect hook while remounting.

    import { markEffectHookIsOnetime } from 'react-fiber-keep-alive';
    
    markEffectHookIsOnetime(effectHook);
    
    // Example:
    React.useEffect(markEffectHookIsOnetime(() => {
        // do something
    }), []);
    React.useLayoutEffect(markEffectHookIsOnetime(() => {
        // do something
    }), []);
  • KeepAlive.Context (since 0.7.0)

    import * as React from 'react';
    import KeepAlive, { Context, KeepAliveCache } from 'react-fiber-keep-alive';
    import LRUCache from 'lru-cache';
    
    /// Example: Use LRU to manage the cache
    const YourKeepAliveProvider: React.FC<{
        children: React.ReactNode;
        value: null | HTMLElement;
    }> = (props) => {
        const container = props.value;
        const context: Context = React.useMemo(() => {
            if (!container) {
                return [];
            }
            const caches = new LRUCache<string, KeepAliveCache>({ 
                max: 10,
            });
            return [container, caches, new Map()];
        }, []);
    
        return (
            <KeepAlive.Context.Provider value={context}>
                {props.children}
            </KeepAlive.Context.Provider>
        );
    };

💡 Tips

  • Be careful the global side effects. (ex: insert global style)
  • Do not use <KeepAlive> under the <React.StrictMode>.
  • Recursive <KeepAlive> handled by top level <KeepAlive>.
  • If the container changed in ReactDOM.createPortal(children, container).
    • All saved sub tree state will be lost.
  • Errors from react-devtools after <KeepAlive> remounted.
    • Try force refresh the components tree. (ex: updates components filter)

🏁 Tested

Examples

React v16.8+ / v17 / v18

  • render(children, container)
  • hydrate(children, container)

React v18 (concurrent mode)

  • createRoot(container).render(children)
  • hydrateRoot(container, children)

Class Component

  • Component.getDerivedStateFromProps()
  • Component.getDerivedStateFromError()
  • instance.componentDidMount()
  • instance.getSnapshotBeforeUpdate()
  • instance.componentDidUpdate()
  • instance.componentWillUnmount()
  • instance.render()

Function Component

  • useContext()
  • useCallback()
  • useEffect()
  • useImperativeHandle()
  • useLayoutEffect()
  • useMemo()
  • useRef()
  • useState()
  • useDebugValue()
  • useDeferredValue() (since v18)
  • useId() (since v18)
  • useInsertionEffect() (since v18)
  • useSyncExternalStore() (since v18)
  • useTransition() (since v18)

Other

  • ReactDOM.createPortal(children, container)
  • React.memo()
  • React.forwardRef()
  • React.lazy()
  • <Suspense>
  • <Offscreen> (since v18)

🐛 Issues

If you find a bug, please file an issue on our issue tracker on GitHub.

📄 License

Copyright © 2022 Shen JunruMIT license.