npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@hzzt/react-activation

v0.0.4

Published

<KeepAlive /> for React like <keep-alive /> in vue

Downloads

5

Readme

React Activation

size dm

English | 中文说明

Implementation of the <keep-alive /> function in Vue For React


More stable <KeepAlive /> function with babel pre-compilation

Online Demo


More examples


Compatibility

  • React v17+ (beta)

  • React v16+

  • Preact v10+

  • Compatible with SSR


Install

yarn add react-activation
# or
npm install react-activation

Usage

1. Add react-activation/babel plugins in .babelrc

Why is it needed?

The plugin adds a _nk attribute to each JSX element during compilation to help the react-activation runtime generate an unique identifier by render location base on react-node-key.

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

2. In your business code, place the <AliveScope> outer layer at a location that will not be unmounted, usually at the application entrance

Note: When used with react-router or react-redux, you need to place <AliveScope> inside <Router> or <Provider>

// entry.js

import React from 'react'
import ReactDOM from 'react-dom'
import { AliveScope } from 'react-activation'

import Test from './Test'

ReactDOM.render(
  <AliveScope>
    <Test />
  </AliveScope>,
  document.getElementById('root')
)

3. Wrap the components that need to keep states with <KeepAlive>

Like the <Counter> component in the example

// Test.js

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 Test() {
  const [show, setShow] = useState(true)

  return (
    <div>
      <button onClick={() => setShow(show => !show)}>Toggle</button>
      {show && (
        <KeepAlive>
          <Counter />
        </KeepAlive>
      )}
    </div>
  )
}

export default Test

Lifecycle

ClassComponent works with withActivation decorator

Use componentDidActivate and componentWillUnactivate to correspond to the two states of "activate" and "unactivate" respectively.

FunctionComponent uses the useActivate and useUnactivate hooks respectively

...
import KeepAlive, { useActivate, useUnactivate, withActivation } from 'react-activation'

@withActivation
class TestClass extends Component {
  ...
  componentDidActivate() {
    console.log('TestClass: componentDidActivate')
  }

  componentWillUnactivate() {
    console.log('TestClass: componentWillUnactivate')
  }
  ...
}
...
function TestFunction() {
  useActivate(() => {
    console.log('TestFunction: didActivate')
  })

  useUnactivate(() => {
    console.log('TestFunction: willUnactivate')
  })
  ...
}
...
function App() {
  ...
  return (
    {show && (
      <KeepAlive>
        <TestClass />
        <TestFunction />
      </KeepAlive>
    )}
  )
}
...

Save Scroll Position (true by default)

<KeepAlive /> would try to detect scrollable nodes in its children, then, save their scroll position automaticlly before componentWillUnactivate and restore saving position after componentDidActivate

If you don't want <KeepAlive /> to do this thing, set saveScrollPosition prop to false

<KeepAlive saveScrollPosition={false} />

If your components share screen scroll container, document.body or document.documentElement, set saveScrollPosition prop to "screen" can save sharing screen container's scroll position before componentWillUnactivate

<KeepAlive saveScrollPosition="screen" />

Multiple Cache

Under the same parent node, <KeepAlive> in the same location will use the same cache by default.

For example, with the following parameter routing scenario, the /item route will be rendered differently by id, but only the same cache can be kept.

<Route
  path="/item/:id"
  render={props => (
    <KeepAlive>
      <Item {...props} />
    </KeepAlive>
  )}
/>

Similar scenarios, you can use the id attribute of <KeepAlive> to implement multiple caches according to specific conditions.

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

Cache Control

Automatic control cache

Add the when attribute to the <KeepAlive /> tag that needs to control the cache. The value is as follows

When the when type is Boolean

  • true: Cache after uninstallation
  • false: Not cached after uninstallation
<KeepAlive when={false}>

When the when type is Array

The 1th parameter indicates whether it needs to be cached at the time of uninstallation.

The 2th parameter indicates whether to unload all cache contents of <KeepAlive>, including all <KeepAlive> nested in <KeepAlive>.

// For example:
// The following indicates that it is not cached when uninstalling
// And uninstalls all nested `<KeepAlive>`
<KeepAlive when={[false, true]}>
  ...
  <KeepAlive>
    ...
    <KeepAlive>...</KeepAlive>
    ...
  </KeepAlive>
  ...
</KeepAlive>

When the when type is Function

The return value is the above Boolean or Array, which takes effect as described above.

Manually control the cache

  1. Add the name attribute to the <KeepAlive> tag that needs to control the cache.

  2. Get control functions using withAliveScope or useAliveController

    • drop(name)

      Unload the <KeepAlive> node in cache state by name. The name can be of type String or RegExp. Note that only the first layer of content that hits <KeepAlive> is unloaded and will not be uninstalled in <KeepAlive>. Would not unload nested <KeepAlive>

    • dropScope(name)

      Unloads the <KeepAlive> node in cache state by name. The name optional type is String or RegExp, which will unload all content of <KeepAlive>, including all <KeepAlive> nested in <KeepAlive>.

    • clear()

      will clear all <KeepAlive> in the cache

    • getCachingNodes()

      Get all the nodes in the cache

...
import KeepAlive, { withAliveScope, useAliveController } from 'react-activation'
...
<KeepAlive name="Test">
  ...
    <KeepAlive>
      ...
        <KeepAlive>
          ...
        </KeepAlive>
      ...
    </KeepAlive>
  ...
</KeepAlive>
...
function App() {
  const { drop, dropScope, clear, getCachingNodes } = useAliveController()

  useEffect(() => {
    drop('Test')
    // or
    drop(/Test/)
    // or
    dropScope('Test')

    clear()
  })

  return (
    ...
  )
}
// or
@withAliveScope
class App extends Component {
  render() {
    const { drop, dropScope, clear, getCachingNodes } = this.props

    return (
      ...
    )
  }
}
...

Principle

Pass the children attribute of <KeepAlive /> to <AliveScope /> and render it with <Keeper />

After rendering <Keeper />, the content is transferred to <KeepAlive /> through DOM operation.

Since <Keeper /> will not be uninstalled, caching can be implemented.

Simplest Implementation Demo


Breaking Change

  1. <KeepAlive /> needs to pass children to <AliveScope /> , so the rendering of the real content will be slower than the normal situation

    Will have a certain impact on the function of strictly relying on the lifecycle order, such as getting the value of ref in componentDidMount, as follows

    class Test extends Component {
      componentDidMount() {
        console.log(this.outside) // will log <div /> instance
        console.log(this.inside) // will log undefined
      }
    
      render() {
        return (
          <div>
            <div
              ref={ref => {
                this.outside = ref
              }}
            >
              Outside KeepAlive
            </div>
            <KeepAlive>
              <div
                ref={ref => {
                  this.inside = ref
                }}
              >
                Inside KeepAlive
              </div>
            </KeepAlive>
          </div>
        )
      }
    }

    The above error in ClassComponent can be fixed by using the withActivation high-level component

    FunctionComponent currently has no processing method, you can use setTimeout or nextTick to delay ref getting behavior

    @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>
        )
      }
    }
  2. Destructive impact on Context, need to be manually fixed

    Problem reference: https://github.com/StructureBuilder/react-keep-alive/issues/36

    <Provider value={1}>
      {show && (
        <KeepAlive>
          <Consumer>
            {(
              context // Since the rendering level is broken, the context cannot be obtained here.
            ) => <Test contextValue={context} />}
          </Consumer>
        </KeepAlive>
      )}
      <button onClick={toggle}>toggle</button>
    </Provider>

    Choose a repair method

    • Create Context using createContext exported from react-activation

    • Fix the affected Context with fixContext exported from react-activation

    ...
    import { createContext } from 'react-activation'
    
    const { Provider, Consumer } = createContext()
    ...
    // or
    ...
    import { createContext } from 'react'
    import { fixContext } from 'react-activation'
    
    const Context = createContext()
    const { Provider, Consumer } = Context
    
    fixContext(Context)
    ...
  3. Affects the functionality that depends on the level of the React component, as follows

    • [x] Fix withRouter/hooks of react-router
    • [x] ~~Error Boundaries~~ (Fixed)
    • [x] ~~React.Suspense & React.lazy~~ (Fixed)
    • [ ] React Synthetic Event Bubbling Failure
    • [ ] Other undiscovered features