@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 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-->
@@ -0,0 +1,5 @@
1
+ import { useState } from 'react';
2
+
3
+ declare const useRafState: typeof useState;
4
+
5
+ export { useRafState };
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
+ }