Skip to content

Commit

Permalink
feat: add new Hook useVersionedAction to run versioned action
Browse files Browse the repository at this point in the history
  • Loading branch information
vikiboss committed Sep 11, 2024
1 parent 58e04c6 commit f34bfa7
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/scripts/generate-hooks-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

const hooksSrc = resolve(__dirname, '../../packages/react-use/src')
const ignoredDirs = ['utils', 'use-track-ref-state', 'use-versioned-action', 'use-web-observer']
const ignoredDirs = ['utils', 'use-track-ref-state', 'use-web-observer']

const dirents = await fs.readdir(hooksSrc, { withFileTypes: true })
const hooksDirents = dirents.filter((d) => d.isDirectory() && ignoredDirs.every((e) => e !== d.name))
Expand Down
30 changes: 30 additions & 0 deletions packages/react-use/src/use-versioned-action/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Button, Card, KeyValue, OTP, Zone, wait as mockFetch } from '@/components'
import { useSafeState, useVersionedAction } from '@shined/react-use'

export function App() {
const [value, setValue] = useSafeState('Click to Fetch')
const [incVersion, runVersionedAction] = useVersionedAction()

const fetch = async () => {
await mockFetch(300 + Math.random() * 1_000)
setValue(OTP())
}

const versionedFetch = async () => {
const version = incVersion()
await mockFetch(300 + Math.random() * 1_000)
runVersionedAction(version, () => {
setValue(OTP())
})
}

return (
<Card>
<KeyValue label="Value" value={value} />
<Zone>
<Button onClick={fetch}>Fetch</Button>
<Button onClick={versionedFetch}>Versioned Fetch</Button>
</Zone>
</Card>
)
}
72 changes: 72 additions & 0 deletions packages/react-use/src/use-versioned-action/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
category: Utilities
features: ['LowLevel']
---

# useVersionedAction

import { HooksType, Since } from '@/components'

<HooksType {...frontmatter} />

<Since version='v1.7.0' />

A low-level React Hook designed to facilitate the use of "versioned" actions, commonly employed in asynchronous scenarios for filtering operations to ensure only the latest action is executed.

## Scenes \{#scenes}

- **Asynchronous operation filtering**: Handling multiple asynchronous operations, executing only the latest operation, ignoring "expired" ones.

## Demo \{#demo}

import { App } from './demo'

<App />

## Usage \{#usage}

When `doSomething()` is called consecutively, only the most recent operation is executed, avoiding "expired" operations that could lead to screen flicker or unexpected results.

```tsx
const [incVersion, runVersionedAction] = useVersionedAction()

const doSomething = async () => {
// Increment the version number with incVersion()
const version = incVersion()

// Asynchronous operation, like fetching data
const result = await fetchSomething()

// Ensure only the latest action is executed with runVersionedAction()
runVersionedAction(version, async () => {
setResult(result)
})
}
```

## Source \{#source}

import { Source } from '@/components'

<Source />

## API

```tsx
const [incVersion, runVersionedAction] = useVersionedAction()
```

### Returns \{#returns}

```tsx
export type UseVersionedActionReturns = readonly [
/**
* Increment the version number and return the current version number
*/
incVersion: () => number,
/**
* Executes the versioned operation, only if the version number matches, ensuring that only the latest action is executed
*/
runVersionedAction: <T extends AnyFunc>(version: number, handler: T) => ReturnType<T> | undefined,
]
```
17 changes: 14 additions & 3 deletions packages/react-use/src/use-versioned-action/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@ import { useStableFn } from '../use-stable-fn'

import type { AnyFunc } from '../utils/basic'

export function useVersionedAction() {
export type UseVersionedActionReturns = readonly [
/**
* Increase the version
*/
incVersion: () => number,
/**
* Run the action with the specified version
*/
runVersionedAction: <T extends AnyFunc>(version: number, handler: T) => ReturnType<T> | undefined,
]

export function useVersionedAction(): UseVersionedActionReturns {
const versionRef = useRef(0)

const incVersion = useStableFn(() => ++versionRef.current)

const runVersionedAction = useStableFn((version: number, handler: AnyFunc) => {
const runVersionedAction = useStableFn(<T extends AnyFunc>(version: number, handler: T) => {
if (version !== versionRef.current) return
return handler()
return handler() as ReturnType<T>
})

return [incVersion, runVersionedAction] as const
Expand Down
74 changes: 74 additions & 0 deletions packages/react-use/src/use-versioned-action/index.zh-cn.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
category: Utilities
features: ['LowLevel']
---

# useVersionedAction

import { HooksType, Since } from '@/components'

<HooksType {...frontmatter} />

<Since version='v1.7.0' />

一个用于帮助使用“带版本”动作的 React Hook,常用于异步场景下进行操作过滤,以保证只执行最新的操作。

## 场景 \{#scenes}

- **异步操作过滤**:处理多个异步操作,只执行最新的操作,忽略“已过期”的操作。

## 演示 \{#demo}

快速点击下面的两个按钮,只有“带版本”的操作会忽略“已过期”的操作,确保只执行最新的操作。

import { App } from './demo'

<App />

## 用法 \{#usage}

`doSomething()` 被连续调用时,只有最新的操作会被执行,而不会出现“过期”的操作导致的「屏闪」或者「非预期结果」。

```tsx
const [incVersion, runVersionedAction] = useVersionedAction()

const doSomething = async () => {
// 通过 incVersion() 来增加版本号
const version = incVersion()

// 异步操作,如请求数据
const result = await fetchSomething()

// 通过 runVersionedAction() 来确保只执行最新的操作
runVersionedAction(version, async () => {
setResult(result)
})
}
```

## 源码 \{#source}

import { Source } from '@/components'

<Source />

## API

```tsx
const [incVersion, runVersionedAction] = useVersionedAction()
```

### 返回值 \{#returns}

```tsx
export type UseVersionedActionReturns = readonly [
/**
* 增加版本号,并返回当前版本号
*/
incVersion: () => number,
/**
* 执行带版本号的操作,只有当版本号匹配时才会执行,以确保只执行最新的操作
*/
runVersionedAction: <T extends AnyFunc>(version: number, handler: T) => ReturnType<T> | undefined,
]
```

0 comments on commit f34bfa7

Please sign in to comment.