Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

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

Hack <KeepAlive /> for React
https://github.com/CJY0208/react-activation

cache cache-control keep-alive keepalive offscreen react

Last synced: about 1 month ago
JSON representation

Hack <KeepAlive /> for React

Awesome Lists containing this project

README

        

### NOTICE

- DO NOT use ``
- (React v18+) DO NOT use `ReactDOMClient.createRoot`, use `ReactDOM.render` instead, https://github.com/CJY0208/react-activation/issues/225#issuecomment-1311136388

# React Activation

[![size](https://img.shields.io/bundlephobia/minzip/[email protected])](https://bundlephobia.com/result?p=react-activation@latest)
[![dm](https://img.shields.io/npm/dm/react-activation.svg)](https://github.com/CJY0208/react-activation)
![](https://komarev.com/ghpvc/?username=cjy0208-react-activation&label=VIEWS)

English | [中文说明](./README_CN.md)

**HACK Implementation** of the `` function in `Vue` For `React`

Please also pay attention to official support [``](https://github.com/reactwg/react-18/discussions/19) in `React 18.x`

---

More stable `` function with `babel` pre-compilation

[Online Demo](https://codesandbox.io/s/affectionate-beaver-solkt)

---

## More examples

- [Closable tabs with `react-router`](https://codesandbox.io/s/keguanbideyifangwenluyou-tab-shilikeanluyoucanshufenduofenhuancun-ewycx)
- [Closable tabs with `umi`](https://codesandbox.io/s/umi-keep-alive-tabs-demo-knfxy)
- [Using Animation with `react-router`](https://codesandbox.io/s/luyouzhuanchangdonghuashili-jdhq1)

---

## Compatibility

- React v16 / v17 / v18

- Preact v10+

- Compatible with SSR

---

## Install

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

---

## Usage

#### 1. (Optional, Recommended) Add `react-activation/babel` plugins in `.babelrc`

[Why is it needed?](https://github.com/CJY0208/react-activation/issues/18#issuecomment-564360695)

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`](https://github.com/CJY0208/react-node-key).

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

**(0.11.0+)** If you don't want to use Babel, it is recommended that each `` declare a globally unique and invariant `cacheKey` attribute to ensure the stability of the cache, as follows:

```jsx

```

#### 2. Wrap the components that need to keep states with ``

Like the `` component in the example

```javascript
// App.js

import React, { useState } from 'react'
import KeepAlive from 'react-activation'

function Counter() {
const [count, setCount] = useState(0)

return (


count: {count}


setCount(count => count + 1)}>Add

)
}

function App() {
const [show, setShow] = useState(true)

return (


setShow(show => !show)}>Toggle
{show && (



)}

)
}

export default App
```

#### 3. Place the `` 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 `` inside `` or ``

```javascript
// index.js

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

import App from './App'

ReactDOM.render(


,
document.getElementById('root')
)
```

---

## 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

```javascript
...
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 && (




)}
)
}
...
```

---

## Cache Control

### Manually control the cache

1. Add the `name` attribute to the `` tag that needs to control the cache.

2. Get control functions using `withAliveScope` or `useAliveController`.

- **drop(name)**: (`drop` can only be used for nodes in the cache state. If the node is not cached but needs to clear the cache state, please use `refresh`)

Unload the `` 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 `` is unloaded and will not be uninstalled in ``. Would not unload nested ``.

- **dropScope(name)**: (`dropScope` can only be used for nodes in the cache state. If the node is not cached but needs to clear the cache state, please use `refreshScope`)

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

- **refresh(name)**:

Refresh the `` 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 `` is refreshed and will not be uninstalled in ``. Would not refresh nested ``.

- **refreshScope(name)**:

Refresh the `` node in cache state by name. The name optional type is `String` or `RegExp`, which will refresh all content of ``, including all `` nested in ``.

- **clear()**:

will clear all `` in the cache

- **getCachingNodes()**:

Get all the nodes in the cache

```javascript
...
import KeepAlive, { withAliveScope, useAliveController } from 'react-activation'
...

...

...

...

...

...

...
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 (
...
)
}
}
...
```

---

### Automatic control cache

Add the `when` attribute to the `` 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

```javascript

```

#### 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 ``, including all `` nested in ``.

```javascript
// For example:
// The following indicates that it is not cached when uninstalling
// And uninstalls all nested ``

...

...
...
...

...

```

#### When the `when` type is `Function` (**Recommended**)

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

The final calculation time of `when` is adjusted to `componentWillUnmount` lifecicle of ``, the problem that most of the `when` do not achieve the expected effect can be avoided.

```jsx
true}>
[false, true]}>
```

---

## Multiple Cache

Under the same parent node, `` 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.

```javascript
(



)}
/>
```

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

```javascript
(



)}
/>
```

---

## Save Scroll Position (`true` by default)

`` 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 `` to do this thing, set `saveScrollPosition` prop to `false`

```javascript

```

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`

```javascript

```

---

## Principle

Pass the `children` attribute of `` to `` and render it with ``

After rendering ``, the content is transferred to `` through `DOM` operation.

Since `` will not be uninstalled, caching can be implemented.

[Simplest Implementation Demo](https://codesandbox.io/s/zuijian-react-keepalive-shixian-ovh90)

---

## Breaking Change

1. `` needs to pass children to `` , 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

```javascript
class Test extends Component {
componentDidMount() {
console.log(this.outside) // will log

instance
console.log(this.inside) // will log undefined
}

render() {
return (


{
this.outside = ref
}}
>
Outside KeepAlive


{
this.inside = ref
}}
>
Inside KeepAlive



)
}
}
```

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

```javascript
@withActivation
class Test extends Component {
componentDidMount() {
console.log(this.outside) // will log

instance
console.log(this.inside) // will log
instance
}

render() {
return (


{
this.outside = ref
}}
>
Outside KeepAlive


{
this.inside = ref
}}
>
Inside KeepAlive



)
}
}
```

2. Destructive impact on `Context`

after `[email protected]` with `[email protected]+`, this question has been automatic fixed

`[email protected]` with `react@17+` you Need to make the following changes to achieve automatic repair

```jsx
import { autoFixContext } from 'react-activation'

autoFixContext(
[require('react/jsx-runtime'), 'jsx', 'jsxs', 'jsxDEV'],
[require('react/jsx-dev-runtime'), 'jsx', 'jsxs', 'jsxDEV']
)
```

Versions below `[email protected]` need to be repaired manually, refer to the following

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

```javascript

{show && (


{(
context // Since the rendering level is broken, the context cannot be obtained here.
) => }


)}
toggle

```

Choose a repair method

- Create `Context` using `createContext` exported from `react-activation`

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

```javascript
...
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](https://github.com/CJY0208/react-activation/issues/77)
- [x] ~~Error Boundaries~~ (Fixed)
- [x] ~~React.Suspense & React.lazy~~ (Fixed)
- [ ] React Synthetic Event Bubbling Failure
- [ ] Other undiscovered features

---