@use-raf/state 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# @use-raf/state
|
|
2
|
+
|
|
3
|
+
A React hook that synchronizes state updates with `requestAnimationFrame` for optimal rendering performance.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @use-raf/state
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { useRafState } from '@use-raf/state'
|
|
15
|
+
|
|
16
|
+
const Demo = () => {
|
|
17
|
+
const [count, setCount] = useRafState(0)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div>
|
|
21
|
+
<p>Count: {count}</p>
|
|
22
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
23
|
+
<button onClick={() => setCount(prev => prev - 1)}>Decrement</button>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
High-frequency updates example:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
const Demo = () => {
|
|
33
|
+
const [position, setPosition] = useRafState({ x: 0, y: 0 })
|
|
34
|
+
|
|
35
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
36
|
+
// Updates are automatically batched to the next frame
|
|
37
|
+
setPosition({ x: e.clientX, y: e.clientY })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div onMouseMove={handleMouseMove}>
|
|
42
|
+
<p>Position: {position.x}, {position.y}</p>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
### `useRafState<S>(initialState?)`
|
|
51
|
+
|
|
52
|
+
**Parameters:**
|
|
53
|
+
|
|
54
|
+
- `initialState: S | (() => S)` - Initial state value or lazy initializer function
|
|
55
|
+
|
|
56
|
+
**Returns:**
|
|
57
|
+
|
|
58
|
+
- `[state, setState]` - Current state and setter function (identical to `useState` API)
|
|
59
|
+
|
|
60
|
+
**Note:** When multiple updater functions are called between frames (e.g., `setState(prev => prev + 1)` three times), `@use-raf/state` correctly chains them, resulting in `3`, while other implementations (`@shined/react-use`, `react-use`, `@reactuses/core`) only apply the last update, resulting in `1`. See [the trial test case](./src/state.test.ts#L367-L410):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bun run test:trial
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Another example illustrating this behavior:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const [count, setCount] = useRafState(0)
|
|
70
|
+
|
|
71
|
+
const double = useCallback(() => {
|
|
72
|
+
setCount(v => v + 1)
|
|
73
|
+
setCount(v => v + 1)
|
|
74
|
+
}, [])
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```mermaid
|
|
78
|
+
flowchart LR
|
|
79
|
+
F1["..."] --> F2["setState(1)"]
|
|
80
|
+
F2 --> F3["..."]
|
|
81
|
+
F3 --> RP1["Repaint (state = 1)"]
|
|
82
|
+
RP1 --> F4["..."]
|
|
83
|
+
F4 --> F5["setState(v => v + 1)"]
|
|
84
|
+
F5 --> F6["setState(v => v + 1)"]
|
|
85
|
+
F6 --> F7["..."]
|
|
86
|
+
F7 --> RP2["Repaint (state = 3)"]
|
|
87
|
+
RP2 --> F8["..."]
|
|
88
|
+
|
|
89
|
+
subgraph DoubleCallback["double callback"]
|
|
90
|
+
F5
|
|
91
|
+
F6
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
style F1 fill:#cccccc,stroke:#888,stroke-width:2px,color:#000
|
|
95
|
+
style F2 fill:#cccccc,stroke:#888,stroke-width:2px,color:#000
|
|
96
|
+
style F3 fill:#cccccc,stroke:#888,stroke-width:2px,color:#000
|
|
97
|
+
style RP1 fill:#4A90E2,stroke:#07c,stroke-width:3px,color:#fff
|
|
98
|
+
style F4 fill:#cccccc,stroke:#888,stroke-width:2px,color:#000
|
|
99
|
+
style F5 fill:#cccccc,stroke:#888,stroke-width:2px,color:#000
|
|
100
|
+
style F6 fill:#cccccc,stroke:#888,stroke-width:2px,color:#000
|
|
101
|
+
style F7 fill:#cccccc,stroke:#888,stroke-width:2px,color:#000
|
|
102
|
+
style RP2 fill:#4A90E2,stroke:#07c,stroke-width:3px,color:#fff
|
|
103
|
+
style F8 fill:#cccccc,stroke:#888,stroke-width:2px,color:#000
|
|
104
|
+
style DoubleCallback fill:#9B8CF5,stroke:#6C5CDC,stroke-width:3px,color:#fff
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
| Package | Result |
|
|
108
|
+
|---------|--------|
|
|
109
|
+
| `@use-raf/state` | ✅ `3` |
|
|
110
|
+
| `@shined/react-use` | ❌ `2` |
|
|
111
|
+
| `react-use` | ❌ `2` |
|
|
112
|
+
| `@reactuses/core` | ❌ `2` |
|
|
113
|
+
|
|
114
|
+
## Performance
|
|
115
|
+
|
|
116
|
+
Benchmarked against similar hooks from popular libraries. While `react-use` leads in all scenarios, `@use-raf/state` remains competitive with both the best-performing `react-use` and `@shined/react-use`, having similar results with the last one across throughput tests.
|
|
117
|
+
|
|
118
|
+
### Benchmark Results
|
|
119
|
+
|
|
120
|
+
| Test | @use-raf/state | react-use | @shined/react-use | @reactuses/core |
|
|
121
|
+
|------|----------------|-----------|-------------------|-----------------|
|
|
122
|
+
| Mount cost (hz) | 16,092.44 | **17,850.62** | 16,330.88 | 17,411.35 |
|
|
123
|
+
| Single update/frame (hz) | 158.50 | **192.33** | 166.33 | 53.28 |
|
|
124
|
+
| Batched updates (hz) | 541.59 | **566.57** | 515.62 | 323.48 |
|
|
125
|
+
| Updater functions (hz) | 170.13 | **196.69** | 172.77 | 52.55 |
|
|
126
|
+
| Complex state (hz) | 272.92 | **315.56** | 274.91 | 100.91 |
|
|
127
|
+
|
|
128
|
+
*Higher hz values indicate better performance. Bold values represent the best result for each test.*
|
|
129
|
+
|
|
130
|
+
**Hardware:** Apple M2 Max (12 cores @ 3.50 GHz), 32 GB RAM
|
|
131
|
+
|
|
132
|
+
**Running benchmarks:**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
bun run bench
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Performance regression tracking:** benchmark results are continuously monitored via [CodSpeed][codspeed-link] to detect performance regressions.
|
|
139
|
+
|
|
140
|
+
<!--links:start-->
|
|
141
|
+
[codspeed-link]: https://codspeed.io/einouqo/use-raf
|
|
142
|
+
<!--links:end-->
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{setFrame as u}from"@use-raf/skd";import{useCallback as S,useEffect as p,useRef as c,useState as m}from"react";var i=o=>{let[r,a]=m(o),s=c(r),t=c(),f=S(e=>{let n=e instanceof Function?e(s.current):e;s.current=n,t.current?.(),t.current=u(()=>a(()=>n))},[]);return p(()=>()=>t.current?.(),[]),[r,f]};export{i as useRafState};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/state.hook.ts"],"sourcesContent":["import type { Cancel } from '@use-raf/skd'\nimport { setFrame } from '@use-raf/skd'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport const useRafState: typeof useState = <S>(initialState?: S | (() => S)) => {\n const [state, setState] = useState(initialState)\n const stateRef = useRef(state)\n\n const cancelRef = useRef<Cancel>()\n\n const set = useCallback<typeof setState>((value) => {\n const next = value instanceof Function ? (value as (prev?: S) => S)(stateRef.current) : value\n\n stateRef.current = next\n\n cancelRef.current?.()\n cancelRef.current = setFrame(() => setState(() => next))\n }, [])\n\n useEffect(() => () => cancelRef.current?.(), [])\n\n return [state, set]\n}\n"],"mappings":"AACA,OAAS,YAAAA,MAAgB,eACzB,OAAS,eAAAC,EAAa,aAAAC,EAAW,UAAAC,EAAQ,YAAAC,MAAgB,QAElD,IAAMC,EAAmCC,GAAiC,CAC/E,GAAM,CAACC,EAAOC,CAAQ,EAAIJ,EAASE,CAAY,EACzCG,EAAWN,EAAOI,CAAK,EAEvBG,EAAYP,EAAe,EAE3BQ,EAAMV,EAA8BW,GAAU,CAClD,IAAMC,EAAOD,aAAiB,SAAYA,EAA0BH,EAAS,OAAO,EAAIG,EAExFH,EAAS,QAAUI,EAEnBH,EAAU,UAAU,EACpBA,EAAU,QAAUV,EAAS,IAAMQ,EAAS,IAAMK,CAAI,CAAC,CACzD,EAAG,CAAC,CAAC,EAEL,OAAAX,EAAU,IAAM,IAAMQ,EAAU,UAAU,EAAG,CAAC,CAAC,EAExC,CAACH,EAAOI,CAAG,CACpB","names":["setFrame","useCallback","useEffect","useRef","useState","useRafState","initialState","state","setState","stateRef","cancelRef","set","value","next"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@use-raf/state",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React frame-synchronized state hook",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react",
|
|
28
|
+
"hook",
|
|
29
|
+
"state",
|
|
30
|
+
"useState",
|
|
31
|
+
"requestAnimationFrame",
|
|
32
|
+
"raf",
|
|
33
|
+
"frame",
|
|
34
|
+
"animation",
|
|
35
|
+
"schedule"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"lint": "biome check",
|
|
40
|
+
"lint:fix": "biome check --write",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"test:trial": "TRIAL=true vitest run",
|
|
44
|
+
"bench": "vitest bench --run --no-file-parallelism --sequence.shuffle",
|
|
45
|
+
"bench:watch": "vitest bench --no-file-parallelism --sequence.shuffle",
|
|
46
|
+
"bench:regress": "vitest bench --run -t '#regress' --no-file-parallelism"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": ">=16.14"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@use-raf/skd": "workspace:*"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@reactuses/core": "^6.1.8",
|
|
56
|
+
"@shined/react-use": "^1.13.1",
|
|
57
|
+
"react-use": "^17.6.0"
|
|
58
|
+
},
|
|
59
|
+
"author": "einouqo",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/einouqo/use-raf"
|
|
63
|
+
},
|
|
64
|
+
"bugs": {
|
|
65
|
+
"url": "https://github.com/einouqo/use-raf/issues"
|
|
66
|
+
},
|
|
67
|
+
"license": "MIT"
|
|
68
|
+
}
|