react-activation

repository·master·Indexed 24 days ago

https://github.com/cjy0208/react-activation

A library providing a <KeepAlive /> component for React, similar to Vue's <keep-alive />, which preserves the state and DOM of components even when they are unmounted from the component tree. It includes <AliveScope> for application-level wrapping, lifecycle hooks like useActivate and useUnactivate, and a Babel plugin for stable cache identification.

Tokens
7.5K
Snippets
19
Records
44
Agent score
84%

What's inside react-activation

  1. How to handle multiple caches for the same route

    master

    By default, multiple <KeepAlive> components at the same position under the same parent will share the same cache. This is problematic for routes with parameters (e.g., /item/1 and /item/2) where you want each ID to have its own unique cache.

    To solve this, use the id prop to differentiate caches based on a unique identifier.

    <Route
      path="/item/:id"
      render={props => (
        <KeepAlive id={props.match.params.id}>
          <Item {...props} />
        </KeepAlive>
      )}
    />
    <Route
      path="/item/:id"
      render={props => (
        <KeepAlive id={props.match.params.id}>
          <Item {...props} />
        </KeepAlive>
      )}
    />
  2. Basic Usage: KeepAlive and AliveScope

    master

    To use react-activation, follow these three steps:

    1. Wrap components that need to preserve state with the <KeepAlive> component.
    2. Wrap your application (or a stable part of it) with <AliveScope>. This should be placed at a location that will not be unmounted (usually the application entrance).
    3. Integration Note: When using react-router or react-redux, place <AliveScope> inside <Router> or <Provider>.

    Example Usage:

    import React, { useState } from 'react'
    import { KeepAlive, AliveScope } from 'react-activation'
    
    function Counter() {
      const [count, setCount] = useState(0)
      return (
        <div>
          <p>count: {count}</p>
          <button onClick={() => setCount(count => count + 1)}>Add</button>
        </div>
      )
    }
    
    function App() {
      const [show, setShow] = useState(true)
      return (
        <div>
          <button onClick={() => setShow(show => !show)}>Toggle</button>
          {show && (
            <KeepAlive>
              <Counter />
            </KeepAlive>
          )}
        </div>
      )
    }
    
    // In your entry point (e.g., index.js)
    ReactDOM.render(
      <AliveScope>
        <App />
      </AliveScope>,
      document.getElementById('root')
    )
  3. Configure Babel plugin for react-activation

    master

    It is highly recommended to add the react-activation/babel plugin to your .babelrc. This plugin adds a _nk attribute to JSX elements during compilation, allowing the runtime to generate unique identifiers based on render location.

    If you choose not to use Babel, you must manually provide a globally unique and invariant cacheKey attribute to every <KeepAlive> component to ensure cache stability.

    {
      "plugins": [
        "react-activation/babel"
      ]
    }
  4. Fixing Context issues in older versions or manual scenarios

    master

    If you are using a version older than 0.8.0, or if you need to manually fix a broken Context, you have two options:

    1. Use react-activation's Context API: Create your context using createContext exported from react-activation instead of react.
    2. Use fixContext: If you must use the standard react Context, pass your Context object to the fixContext function provided by react-activation.
    // Option 1: Use react-activation's createContext
    import { createContext } from 'react-activation'
    const { Provider, Consumer } = createContext()
    
    // Option 2: Use fixContext with standard React context
    import { createContext } from 'react'
    import { fixContext } from 'react-activation'
    
    const Context = createContext()
    const { Provider, Consumer } = Context
    
    fixContext(Context)
  5. Fix ref issues in FunctionComponents

    master
    The withActivation HOC is designed for ClassComponent. For FunctionComponent, there is currently no dedicated processing method. If you encounter issues accessing refs immediately upon mount, use setTimeout or nextTick to delay the logic that relies on those refs.
  6. Fixing lifecycle and ref timing issues with @withActivation

    master

    Due to the implementation of <KeepAlive />, children are passed to <AliveScope /> with a slight delay. This means that in componentDidMount, refs for elements inside a <KeepAlive /> component may be undefined even if they are rendered.

    To fix this in ClassComponent, wrap the component with the @withActivation decorator. For FunctionComponent, you currently need to use setTimeout or nextTick to access the ref after the initial render.

    @withActivation
    class Test extends Component {
      componentDidMount() {
        console.log(this.outside) // will log <div /> instance
        console.log(this.inside) // will log <div /> instance
      }
    
      render() {
        return (
          <div>
            <div
              ref={ref => {
                this.outside = ref
              }}
            >
              Outside KeepAlive
            </div>
            <KeepAlive>
              <div
                ref={ref => {
                  this.inside = ref
                }}
              >
                Inside KeepAlive
              </div>
            </KeepAlive>
          </div>
        )
      }
    }
  7. Fixing Context issues in React 17+

    master

    When using react-activation@0.8.0 or higher with React 17+, you must call autoFixContext to ensure React Context works correctly across the rendering boundaries created by the library. You need to pass the JSX runtime modules to the function.

    import { autoFixContext } from 'react-activation'
    
    autoFixContext(
      [require('react/jsx-runtime'), 'jsx', 'jsxs', 'jsxDEV'],
      [require('react/jsx-dev-runtime'), 'jsx', 'jsxs', 'jsxDEV']
    )
  8. How to use KeepAlive and AliveScope

    master

    To implement KeepAlive functionality, follow these three steps:

    1. Wrap components with <KeepAlive>

    Wrap any component that needs to maintain its state when unmounted with the <KeepAlive> component.

    <KeepAlive>
      <Counter />
    </KeepAlive>

    2. Place <AliveScope> at the application entry point

    Wrap your application (or the relevant part of the tree) with <AliveScope>. This component should be placed in a location that is not unmounted.

    Integration Tip: When using react-router or react-redux, place <AliveScope> inside the <Router> or <Provider>.

    import React from 'react'
    import ReactDOM from 'react-dom'
    import { AliveScope } from 'react-activation'
    import App from './App'
    
    ReactDOM.render(
      <AliveScope>
        <App />
      </AliveScope>,
      document.getElementById('root')
    )
    import React, { useState } from 'react'
    import KeepAlive from 'react-activation'
    
    function Counter() {
      const [count, setCount] = useState(0)
      return (
        <div>
          <p>count: {count}</p>
          <button onClick={() => setCount(count => count + 1)}>Add</button>
        </div>
      )
    }
    
    function App() {
      const [show, setShow] = useState(true)
      return (
        <div>
          <button onClick={() => setShow(show => !show)}>Toggle</button>
          {show && (
            <KeepAlive>
              <Counter />
            </KeepAlive>
          )}
        </div>
      )
    }
    
    export default App
  9. Configure Babel plugin for stable KeepAlive

    master

    It is highly recommended to add the react-activation/babel plugin to your .babelrc configuration. This plugin uses react-node-key to add a _nk attribute to JSX elements during compilation, allowing react-activation to generate unique cache IDs based on render position at runtime.

    {
      "plugins": [
        "react-activation/babel"
      ]
    }

    Note for version 0.11.0+: If you do not use the Babel plugin, you should provide a globally unique and unchanging cacheKey prop to every <KeepAlive> component to ensure cache stability:

    <KeepAlive cacheKey="UNIQUE_ID" />
    {
      "plugins": [
        "react-activation/babel"
      ]
    }
  10. Manage KeepAlive lifecycle with activation events

    master

    The KeepAlive component supports custom lifecycle hooks that trigger when the component is cached or restored. These are useful for triggering data re-fetching or UI updates when a user navigates back to a preserved view.

    Lifecycle Hooks

    • LIFECYCLE_ACTIVATE: Triggered when the component is re-mounted and its state/DOM is restored from cache.
    • LIFECYCLE_UNACTIVATE: Triggered when the component is about to be unmounted and its state/DOM is moved into the cache.

    Note: These hooks are applied via the withActivation HOC. You can implement them as methods on your component class or via the withActivation wrapper.

  11. Critical Compatibility Notices

    master

    To ensure react-activation works correctly, observe the following constraints:

    • DO NOT use <React.StrictMode />.
    • React v18+ Users: By default, react-activation uses autoFreeze: true, which may conflict with ReactDOMClient.createRoot. You have two options:
      1. Use ReactDOM.render instead of createRoot.
      2. Disable autoFreeze to allow compatibility with createRoot, though this may result in performance loss.

    Note: Disabling autoFreeze is done via KeepAlive.defautProps.autoFreeze = false.

    import { KeepAlive } from 'react-activation'
    KeepAlive.defautProps.autoFreeze = false // default 'true'