react-state-basis 0.6.2 → 0.6.4
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 +195 -74
- package/dist/{chunk-E3D6YEKB.mjs → chunk-6GBZSOB3.mjs} +8 -1
- package/dist/{chunk-E3D6YEKB.mjs.map → chunk-6GBZSOB3.mjs.map} +1 -1
- package/dist/client.d.mts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js.map +1 -1
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -2
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/zustand.js.map +1 -1
- package/dist/integrations/zustand.mjs +1 -1
- package/dist/plugin.js +2 -1
- package/dist/production.d.mts +3 -3
- package/dist/production.d.ts +3 -3
- package/dist/production.js +8 -7
- package/dist/production.js.map +1 -1
- package/dist/production.mjs +8 -7
- package/dist/production.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
|
|
7
7
|
# react-state-basis
|
|
8
|
-
### Runtime Architectural Auditor for React
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
### Runtime diagnostics for React state
|
|
10
|
+
|
|
11
|
+
**Basis observes when state updates happen and uses those patterns to highlight state-management issues that can be difficult to spot in a code review or profiler. It does not inspect state values.**
|
|
11
12
|
|
|
12
13
|
[](https://www.npmjs.com/package/react-state-basis)
|
|
13
14
|
[](https://github.com/liovic/react-state-basis/stargazers)
|
|
@@ -20,12 +21,16 @@
|
|
|
20
21
|
## Quick Start
|
|
21
22
|
|
|
22
23
|
### 1. Install
|
|
24
|
+
|
|
23
25
|
```bash
|
|
24
26
|
npm i react-state-basis
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
### 2. Setup
|
|
28
|
-
|
|
29
|
+
### 2. Setup with Vite
|
|
30
|
+
|
|
31
|
+
Add the plugin to your `vite.config.ts`.
|
|
32
|
+
|
|
33
|
+
The Babel plugin labels React hooks automatically, so you can continue importing from `react` as usual.
|
|
29
34
|
|
|
30
35
|
```ts
|
|
31
36
|
import { defineConfig } from 'vite';
|
|
@@ -34,43 +39,55 @@ import { basis } from 'react-state-basis/vite';
|
|
|
34
39
|
|
|
35
40
|
export default defineConfig({
|
|
36
41
|
plugins: [
|
|
37
|
-
react({
|
|
38
|
-
babel: {
|
|
42
|
+
react({
|
|
43
|
+
babel: {
|
|
44
|
+
plugins: [['react-state-basis/plugin']],
|
|
45
|
+
},
|
|
39
46
|
}),
|
|
40
|
-
basis()
|
|
41
|
-
]
|
|
47
|
+
basis(),
|
|
48
|
+
],
|
|
42
49
|
});
|
|
43
50
|
```
|
|
51
|
+
This is the supported setup today. Next.js / SWC is not instrumented yet.
|
|
44
52
|
|
|
45
53
|
### 3. Initialize
|
|
54
|
+
|
|
46
55
|
```tsx
|
|
47
56
|
import { BasisProvider } from 'react-state-basis';
|
|
48
57
|
|
|
49
58
|
root.render(
|
|
50
|
-
<BasisProvider
|
|
59
|
+
<BasisProvider
|
|
51
60
|
debug={true}
|
|
52
|
-
showHUD={true}
|
|
61
|
+
showHUD={true}
|
|
53
62
|
>
|
|
54
63
|
<App />
|
|
55
64
|
</BasisProvider>
|
|
56
65
|
);
|
|
57
66
|
```
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
Set `showHUD={false}` to keep the diagnostics in the console without showing the overlay.
|
|
69
|
+
|
|
70
|
+
### 4. Try it
|
|
71
|
+
|
|
72
|
+
For example:
|
|
61
73
|
|
|
62
74
|
```tsx
|
|
63
75
|
const [a, setA] = useState(0);
|
|
64
76
|
const [b, setB] = useState(0);
|
|
65
77
|
|
|
66
78
|
useEffect(() => {
|
|
67
|
-
setB(a + 1);
|
|
79
|
+
setB(a + 1);
|
|
68
80
|
}, [a]);
|
|
69
81
|
|
|
70
|
-
return
|
|
82
|
+
return (
|
|
83
|
+
<button onClick={() => setA(a + 1)}>
|
|
84
|
+
Update
|
|
85
|
+
</button>
|
|
86
|
+
);
|
|
71
87
|
```
|
|
72
88
|
|
|
73
|
-
|
|
89
|
+
When the button is clicked, Basis can identify the effect-driven update pattern and report where it originated. You should see in your console:
|
|
90
|
+
|
|
74
91
|
```
|
|
75
92
|
⚡ BASIS | DOUBLE RENDER
|
|
76
93
|
📍 Location: YourComponent.tsx
|
|
@@ -78,64 +95,141 @@ Issue: effect_L5 triggers b in a separate frame.
|
|
|
78
95
|
Fix: Derive b during the render phase (remove effect) or wrap in useMemo.
|
|
79
96
|
```
|
|
80
97
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
### 5. Control & Scope
|
|
84
|
-
* **Ghost Mode:** Disable the Matrix UI while keeping console-based forensics active by setting `showHUD={false}` on the provider.
|
|
85
|
-
* **Selective Auditing:** Add `// @basis-ignore` at the top of any file to disable instrumentation. Recommended for:
|
|
86
|
-
* High-frequency animation logic (>60fps)
|
|
87
|
-
* Third-party library wrappers
|
|
88
|
-
* Intentional synchronization (e.g., local mirrors of external caches)
|
|
98
|
+
Detection happens at runtime and timing varies by pattern.
|
|
89
99
|
|
|
90
100
|
---
|
|
91
101
|
|
|
92
102
|
## HUD
|
|
93
103
|
|
|
94
|
-
The optional HUD shows
|
|
104
|
+
The optional HUD shows state updates as they happen.
|
|
95
105
|
|
|
96
106
|
<p align="center">
|
|
97
|
-
<img src="./assets/050Basis.gif" width="800" alt="Basis Demo"
|
|
107
|
+
<img src="./assets/050Basis.gif" width="800" alt="Basis Demo">
|
|
98
108
|
</p>
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
The HUD is useful for seeing individual updates. The console report looks at the observed update graph over time, which can reveal patterns that are harder to see from a single interaction.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## What Basis Looks For
|
|
115
|
+
|
|
116
|
+
Basis does not try to determine whether your state is "correct." Instead, it looks for update patterns that are often worth investigating.
|
|
117
|
+
|
|
118
|
+
### Effect-driven updates
|
|
119
|
+
|
|
120
|
+
A `useEffect` causes another state update immediately after rendering.
|
|
121
|
+
|
|
122
|
+
This can be a sign that some state could be derived during render instead.
|
|
123
|
+
|
|
124
|
+
### Correlated state
|
|
125
|
+
|
|
126
|
+
Two pieces of state repeatedly update within the same time window.
|
|
127
|
+
|
|
128
|
+
For example:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
132
|
+
const [isSuccess, setIsSuccess] = useState(false);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If these values consistently change together, it may be worth checking whether they could be represented by a single state value.
|
|
136
|
+
|
|
137
|
+
Basis reports the correlation; it does not assume that the states should be merged.
|
|
138
|
+
|
|
139
|
+
### Fragmented updates
|
|
140
|
+
|
|
141
|
+
A single interaction causes updates across multiple components, files, contexts, or stores.
|
|
142
|
+
|
|
143
|
+
Sometimes this is intentional. In other cases, it can indicate that state ownership is spread across several places.
|
|
144
|
+
|
|
145
|
+
### Context mirroring
|
|
146
|
+
|
|
147
|
+
Local state is repeatedly updated from Context state.
|
|
148
|
+
|
|
149
|
+
This can create two representations of the same information and is worth reviewing when the local copy does not have an independent purpose.
|
|
150
|
+
|
|
151
|
+
### Update origins
|
|
152
|
+
|
|
153
|
+
When several updates occur together, Basis can use the observed update graph to identify which updates appear upstream of others.
|
|
154
|
+
|
|
155
|
+
This is intended to help investigate a chain of updates rather than simply reporting every downstream symptom.
|
|
156
|
+
|
|
157
|
+
### Infinite update protection
|
|
158
|
+
|
|
159
|
+
Basis includes safeguards to stop its own instrumentation from continuing indefinitely when an application enters a recursive update loop.
|
|
101
160
|
|
|
102
161
|
---
|
|
103
162
|
|
|
104
|
-
|
|
163
|
+
### Important: these are signals, not proofs
|
|
164
|
+
|
|
165
|
+
Basis uses runtime timing and correlation heuristics.
|
|
105
166
|
|
|
106
|
-
|
|
167
|
+
A detected pattern is **not automatically a bug**, and Basis does not know the intent behind your application architecture.
|
|
107
168
|
|
|
108
|
-
|
|
109
|
-
- **⚡ Prime Movers (Root Causes)** - Ignores downstream symptoms and points you to the exact hook or event that started the chain reaction.
|
|
110
|
-
- **⚡ Fragmented Updates** - Detects when a single click forces updates in multiple different files/contexts simultaneously (Tearing risk).
|
|
111
|
-
- **Ω Context Mirroring** - Detects when you redundantly copy Global Context data into Local State (creating two sources of truth).
|
|
112
|
-
- **♊ Duplicate State** - Identifies variables that always update at the exact same time and should be merged (e.g. `isLoading` + `isSuccess`).
|
|
113
|
-
- **🛑 Infinite Loops** - A safety circuit-breaker that kills the auditor before a recursive update freezes your browser.
|
|
169
|
+
Use the results as prompts for investigation rather than as rules for how React code should be written.
|
|
114
170
|
|
|
115
|
-
[
|
|
171
|
+
[See examples and possible fixes →](https://github.com/liovic/react-state-basis/wiki/The-Forensic-Catalog)
|
|
116
172
|
|
|
117
173
|
---
|
|
118
174
|
|
|
119
|
-
## Reports
|
|
175
|
+
## Reports
|
|
176
|
+
|
|
177
|
+
Run:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
window.printBasisReport()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
to print a summary of the observed update graph.
|
|
184
|
+
|
|
185
|
+
The report can include:
|
|
186
|
+
|
|
187
|
+
* **Update sources** - where observed update chains appear to originate.
|
|
188
|
+
* **Fan-out** - which updates are followed by the largest number of downstream updates.
|
|
189
|
+
* **Correlated state** - state variables that repeatedly update together.
|
|
190
|
+
* **Effect-driven updates** - updates that occur as a consequence of effects.
|
|
191
|
+
* **Engine metrics** - runtime measurements collected by the Basis engine.
|
|
192
|
+
|
|
193
|
+
These metrics are diagnostic rather than a score for the quality of your application.
|
|
120
194
|
|
|
121
|
-
###
|
|
122
|
-
Check your entire app's state architecture by running `window.printBasisReport()` in the console.
|
|
195
|
+
### Runtime metrics
|
|
123
196
|
|
|
124
|
-
|
|
125
|
-
* **Efficiency Score:** A rough ratio of independent update sources vs effect-driven follow-up updates. Diagnostic, not a grade.
|
|
126
|
-
* **Sync Issues:** Groups entangled variables into clusters (e.g., Boolean Explosions).
|
|
197
|
+
You can inspect engine metrics with:
|
|
127
198
|
|
|
128
|
-
|
|
129
|
-
|
|
199
|
+
```js
|
|
200
|
+
window.getBasisMetrics()
|
|
201
|
+
```
|
|
130
202
|
|
|
131
203
|
---
|
|
132
204
|
|
|
133
|
-
##
|
|
205
|
+
## Controlling the Instrumentation
|
|
206
|
+
|
|
207
|
+
### Console-only mode
|
|
208
|
+
|
|
209
|
+
Disable the HUD while keeping diagnostics enabled:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<BasisProvider showHUD={false}>
|
|
213
|
+
<App />
|
|
214
|
+
</BasisProvider>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Ignoring files
|
|
218
|
+
|
|
219
|
+
Add:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// @basis-ignore
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
to a file to disable Basis instrumentation for that file.
|
|
134
226
|
|
|
135
|
-
|
|
227
|
+
This can be useful for:
|
|
136
228
|
|
|
137
|
-
*
|
|
138
|
-
*
|
|
229
|
+
* high-frequency animation code
|
|
230
|
+
* third-party library wrappers
|
|
231
|
+
* intentionally synchronized state
|
|
232
|
+
* code where instrumentation is not useful
|
|
139
233
|
|
|
140
234
|
---
|
|
141
235
|
|
|
@@ -143,62 +237,89 @@ Basis is verified against industry-standard codebases to ensure high-fidelity de
|
|
|
143
237
|
|
|
144
238
|
### Zustand
|
|
145
239
|
|
|
146
|
-
|
|
147
|
-
store updates. Store signals appear as Σ in the HUD and health report.
|
|
240
|
+
Basis can observe Zustand store updates alongside React state.
|
|
148
241
|
|
|
149
242
|
```typescript
|
|
150
243
|
import { create } from 'zustand';
|
|
151
244
|
import { basisLogger } from 'react-state-basis/zustand';
|
|
152
245
|
|
|
153
246
|
export const useStore = create(
|
|
154
|
-
basisLogger(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
247
|
+
basisLogger(
|
|
248
|
+
(set) => ({
|
|
249
|
+
theme: 'light',
|
|
250
|
+
|
|
251
|
+
toggleTheme: () =>
|
|
252
|
+
set((state) => ({
|
|
253
|
+
theme: state.theme === 'light' ? 'dark' : 'light',
|
|
254
|
+
})),
|
|
255
|
+
}),
|
|
256
|
+
'MyStore'
|
|
257
|
+
)
|
|
160
258
|
);
|
|
161
259
|
```
|
|
162
260
|
|
|
163
|
-
This
|
|
164
|
-
|
|
261
|
+
This allows React and Zustand updates to appear in the same runtime graph.
|
|
262
|
+
|
|
263
|
+
[See the Zustand example →](./examples/basis-zustand/)
|
|
165
264
|
|
|
166
|
-
|
|
265
|
+
### Planned integrations
|
|
167
266
|
|
|
168
|
-
|
|
267
|
+
XState, React Query, and Redux Toolkit are planned.
|
|
169
268
|
|
|
170
|
-
|
|
269
|
+
Community contributions are welcome.
|
|
171
270
|
|
|
172
271
|
---
|
|
173
272
|
|
|
174
273
|
## Performance & Privacy
|
|
175
274
|
|
|
176
|
-
|
|
177
|
-
**Production:** ~0.01ms per hook (monitoring disabled, ~2-3KB bundle)
|
|
178
|
-
**Privacy:** Only tracks update timing, never state values
|
|
275
|
+
Basis is designed primarily as a development-time diagnostic tool.
|
|
179
276
|
|
|
180
|
-
|
|
277
|
+
* **Development:** instrumentation overhead is designed to remain small; current benchmarks show less than 1ms per update cycle in tested scenarios.
|
|
278
|
+
* **Production:** monitoring is disabled, with a small production footprint.
|
|
279
|
+
* **Privacy:** Basis records update timing and relationships, not application state values.
|
|
280
|
+
|
|
281
|
+
Actual overhead depends on the application and instrumentation configuration.
|
|
282
|
+
|
|
283
|
+
[See benchmarks →](https://github.com/liovic/react-state-basis/wiki/Performance-Forensics)
|
|
181
284
|
|
|
182
285
|
---
|
|
183
286
|
|
|
184
|
-
##
|
|
287
|
+
## Real-World Examples
|
|
288
|
+
|
|
289
|
+
Basis has also been tested against existing open-source applications.
|
|
290
|
+
|
|
291
|
+
* **Excalidraw** - Basis identified a theme synchronization pattern and a possible simplification. [PR #10637](https://github.com/excalidraw/excalidraw/pull/10637) was proposed but not merged.
|
|
292
|
+
* **shadcn-admin** - Basis identified a redundant state pattern in viewport detection hooks. [PR #274](https://github.com/satnaing/shadcn-admin/pull/274) was merged.
|
|
185
293
|
|
|
186
|
-
|
|
294
|
+
These examples are intended as demonstrations of the tool's output, not as claims that every detected pattern represents a defect.
|
|
187
295
|
|
|
188
296
|
---
|
|
189
297
|
|
|
190
|
-
##
|
|
298
|
+
## How It Works
|
|
299
|
+
|
|
300
|
+
Basis observes the timing and relationships between state updates while your application runs.
|
|
301
|
+
|
|
302
|
+
It builds an in-memory representation of those updates and applies heuristics to identify recurring patterns.
|
|
303
|
+
|
|
304
|
+
It does **not** need to inspect the values stored in your state to perform these checks.
|
|
305
|
+
|
|
306
|
+
The analysis is intentionally heuristic. Runtime behavior can show that two things consistently happen together, but it cannot by itself prove why they happen together or whether the relationship is intentional.
|
|
191
307
|
|
|
192
|
-
|
|
308
|
+
For a deeper look at the implementation and underlying model:
|
|
193
309
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
310
|
+
[Read the documentation and theory →](https://github.com/liovic/react-state-basis/wiki)
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Roadmap
|
|
199
315
|
|
|
316
|
+
* ✓ **v0.4.x** - Identify state that repeatedly updates together
|
|
317
|
+
* ✓ **v0.5.x** - Identify local state synchronized from Context
|
|
318
|
+
* → **v0.6.x** - Analyze update fan-out and likely upstream sources
|
|
319
|
+
* **v0.7.x** - Improve detection of derived vs. independent state
|
|
320
|
+
* **v0.8.x** - Explore how much local state components actually use
|
|
200
321
|
|
|
201
|
-
[
|
|
322
|
+
[See the full roadmap →](https://github.com/liovic/react-state-basis/wiki/Roadmap)
|
|
202
323
|
|
|
203
324
|
---
|
|
204
325
|
|
|
@@ -837,6 +837,13 @@ var unregisterVariable = (l) => {
|
|
|
837
837
|
instance.loopCounters.delete(l);
|
|
838
838
|
instance.pausedVariables.delete(l);
|
|
839
839
|
instance.redundantLabels.delete(l);
|
|
840
|
+
instance.graph.delete(l);
|
|
841
|
+
instance.graph.forEach((targets) => targets.delete(l));
|
|
842
|
+
instance.violationMap.delete(l);
|
|
843
|
+
instance.violationMap.forEach((list) => {
|
|
844
|
+
const idx = list.findIndex((v) => v.target === l);
|
|
845
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
846
|
+
});
|
|
840
847
|
};
|
|
841
848
|
var beginEffectTracking = (l) => {
|
|
842
849
|
if (instance.config.debug) instance.currentEffectSource = l;
|
|
@@ -874,4 +881,4 @@ export {
|
|
|
874
881
|
printBasisHealthReport,
|
|
875
882
|
getBasisMetrics
|
|
876
883
|
};
|
|
877
|
-
//# sourceMappingURL=chunk-
|
|
884
|
+
//# sourceMappingURL=chunk-6GBZSOB3.mjs.map
|