react-state-basis 0.3.4 → 0.4.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 CHANGED
@@ -5,13 +5,15 @@
5
5
  <div align="center">
6
6
 
7
7
  # 📐 react-state-basis
8
- ### Runtime state profiler for React
8
+ ### Runtime Architectural Auditor & State Profiler for React
9
9
 
10
10
  [![npm version](https://img.shields.io/npm/v/react-state-basis.svg?style=flat-square)](https://www.npmjs.com/package/react-state-basis)
11
11
  [![GitHub stars](https://img.shields.io/github/stars/liovic/react-state-basis.svg?style=flat-square)](https://github.com/liovic/react-state-basis/stargazers)
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
13
13
 
14
- **Catches redundant state and update chains while your React app runs.**
14
+ **Basis tracks the rhythm of state updates, not the data. It identifies redundant patterns and sync-leaks that standard tools miss.**
15
+
16
+ > Stop relying on architectural intuition. Start measuring architectural debt.
15
17
 
16
18
  </div>
17
19
 
@@ -26,7 +28,7 @@
26
28
  - **Infinite loops** - Circular dependencies that freeze your browser
27
29
  - **Tight coupling** - State variables that should be independent but aren't
28
30
 
29
- It works by tracking *when* state updates happen, not *what* the values are.
31
+ It works by tracking *when* state updates happen (temporal patterns), not *what* the values are.
30
32
 
31
33
  ---
32
34
 
@@ -37,7 +39,7 @@ const [user, setUser] = useState(null);
37
39
  const [isLoggedIn, setIsLoggedIn] = useState(false);
38
40
 
39
41
  useEffect(() => {
40
- setIsLoggedIn(!!user); // Double render - flagged as redundant
42
+ setIsLoggedIn(!!user); // Double render - flagged as sync leak
41
43
  }, [user]);
42
44
 
43
45
  // ✅ Better
@@ -49,14 +51,40 @@ const isLoggedIn = !!user; // Computed, no second render
49
51
 
50
52
  ## See It Work
51
53
 
52
- The optional HUD shows which states update together in real-time:
54
+ The optional HUD shows your state basis matrix in real-time:
53
55
 
54
56
  <p align="center">
55
- <img src="./assets/react-state-basis.gif" width="800" alt="React State Basis Demo" />
57
+ <img src="./assets/react-state-basis-demo.gif" width="800" alt="React State Basis Demo" />
56
58
  </p>
57
59
 
58
60
  ---
59
61
 
62
+ ## Real-World Audits
63
+
64
+ Basis has been tested on major open-source projects to validate detection accuracy:
65
+
66
+ ### Excalidraw (114k+ ⭐)
67
+ **Detected:** Causal Sync Leak in theme state synchronization
68
+ **Issue:** A `useEffect` was manually syncing theme state, causing an unnecessary double render on every theme toggle
69
+ **Fix:** [PR #10637](https://github.com/excalidraw/excalidraw/pull/10637) - Replaced with a computed value
70
+
71
+ <p align="center">
72
+ <img src="./assets/excalidraw-audit.png" width="800" alt="Excalidraw Audit" />
73
+ </p>
74
+
75
+ ### shadcn-admin (10k+ ⭐)
76
+ **Detected:** Redundant state pattern in mobile detection hooks
77
+ **Issue:** Viewport resize events were being synchronized via effects rather than direct subscriptions
78
+ **Fix:** [PR #274](https://github.com/satnaing/shadcn-admin/pull/274) - Optimized subscription pattern
79
+
80
+ <p align="center">
81
+ <img src="./assets/shadcn-admin.png" width="800" alt="shadcn Admin Audit" />
82
+ </p>
83
+
84
+ > **Note:** These are proposed architectural improvements. Basis points out patterns worth investigating - the final decision rests with the maintainer.
85
+
86
+ ---
87
+
60
88
  ## Setup (Vite)
61
89
 
62
90
  ### 1. Install
@@ -91,7 +119,19 @@ root.render(
91
119
  );
92
120
  ```
93
121
 
94
- That's it. The tool runs automatically in development.
122
+ ### 4. Verify It's Working
123
+
124
+ Add this test pattern to any component:
125
+ ```tsx
126
+ const [a, setA] = useState(0);
127
+ const [b, setB] = useState(0);
128
+
129
+ useEffect(() => {
130
+ setB(a); // Basis will flag this
131
+ }, [a]);
132
+ ```
133
+
134
+ Trigger an update (e.g., click a button that calls `setA(1)`). You should see a console alert within ~100ms.
95
135
 
96
136
  ---
97
137
 
@@ -99,213 +139,262 @@ That's it. The tool runs automatically in development.
99
139
 
100
140
  ### Console Alerts
101
141
 
102
- When Basis detects issues, you'll see styled console logs:
103
-
104
- **Redundant State:**
142
+ **Redundant Pattern:**
143
+ Detected when two variables move in perfect unison.
105
144
  ```
106
- 📐 BASIS | REDUNDANT STATE DETECTED
145
+ 📐 BASIS | REDUNDANT PATTERN
107
146
  📍 Location: TodoList.tsx
108
- Pattern: States "todos" and "count" update together 92% of the time.
109
- One is likely redundant and can be removed.
110
-
111
- Suggested fix: Convert "count" to a computed value
147
+ Observation: "todos" and "count" move together.
148
+ One is likely a direct mirror of the other. Confidence: 94%
149
+ Action: Refactor "count" to useMemo.
112
150
  ```
113
151
 
114
- **Update Chains:**
152
+ **Sync Leak (Causal Chain):**
153
+ Detected when one variable consistently triggers another with a temporal lag.
115
154
  ```
116
- 💡 BASIS | UPDATE CHAIN DETECTED
117
- Sequence: user ➔ Effect ➔ isLoggedIn
118
- Pattern: "isLoggedIn" is synchronized via useEffect, causing a second render.
155
+ 💡 BASIS | DETECTED SYNC LEAK
156
+ 📍 Location: AuthProvider.tsx
157
+ Flow: user Effect isLoggedIn
158
+ Context: The engine detected a consistent 20ms lag between these updates.
159
+ Result: This creates a Double Render Cycle.
119
160
  ```
120
161
 
121
- **Infinite Loops:**
162
+ **Infinite Loop:**
163
+ Detected when a variable updates too rapidly (circuit breaker).
122
164
  ```
123
- 🛑 BASIS | INFINITE LOOP DETECTED
124
- State variable "counter" is updating too rapidly (25+ times in 500ms).
125
- Execution halted to prevent browser freeze.
165
+ 🛑 BASIS CRITICAL | CIRCUIT BREAKER
166
+ Infinite oscillation detected on: "counter"
167
+ Execution halted to prevent browser thread lock.
126
168
  ```
127
169
 
128
170
  ### Health Report
129
171
 
130
- Check your entire app's state health:
172
+ Check your entire app's state architecture:
131
173
  ```tsx
132
174
  window.printBasisReport();
133
175
  ```
134
176
 
135
177
  Shows:
136
- - Efficiency score (how much redundant state you have)
137
- - Which states are independent vs synchronized
138
- - Correlation matrix
178
+ - **Health Score** - Percentage of independent vs. synchronized state
179
+ - **Synchronized Clusters** - Groups of variables that move together
180
+ - **Correlation Matrix** - Full pairwise similarity analysis (for <15 variables)
139
181
 
140
182
  ---
141
183
 
142
- ## Testing on Real Projects
143
-
144
- I've tested this on a few open-source projects to validate the detection:
145
-
146
- ### Excalidraw (114k+ ⭐)
147
-
148
- **Found:** Theme state being manually synchronized in `useEffect`
149
- **Issue:** Double render on every theme change
150
- **Fix:** [PR #10637](https://github.com/excalidraw/excalidraw/pull/10637) - replaced with computed value
151
- **Status:** Pending review
152
-
153
- <p align="center">
154
- <img src="./assets/excalidraw-audit.png" width="800" alt="Excalidraw Audit" />
155
- </p>
184
+ ## How It Works (v0.4.0)
156
185
 
157
- ### shadcn-admin (10k+ ⭐)
186
+ ### Temporal Cross-Correlation
158
187
 
159
- **Found:** Mobile detection hook with effect-based synchronization
160
- **Issue:** Unnecessary re-renders on viewport resize
161
- **Fix:** [PR #274](https://github.com/satnaing/shadcn-admin/pull/274) - cleaner subscription pattern
162
- **Status:** Pending review
163
-
164
- <p align="center">
165
- <img src="./assets/shadcn-admin.png" width="800" alt="shadcn Admin Audit" />
166
- </p>
167
-
168
- > **Note:** These are proposed improvements based on the tool's detection. The maintainers may choose different solutions or determine the patterns are intentional.
188
+ Basis tracks **when** state updates occur, creating a 50-tick timeline for each variable:
189
+ ```
190
+ useState("count"): [0,0,1,0,0,1,1,0,...] (updates at ticks 2, 5, 6)
191
+ useState("total"): [0,0,1,0,0,1,1,0,...] (same pattern)
192
+ ↑ Redundant: identical temporal signature
193
+ ```
169
194
 
170
- ---
195
+ For every pair of variables, Basis checks three temporal relationships:
171
196
 
172
- ## How It Works
197
+ 1. **Synchronous (Redundancy):** Do they update in the same tick?
198
+ ```
199
+ A: [0,1,0,1,0,...]
200
+ B: [0,1,0,1,0,...] → Flagged as redundant
201
+ ```
173
202
 
174
- ### The Basic Idea
203
+ 2. **Lead-Lag (A → B):** Does B consistently follow A in the next tick?
204
+ ```
205
+ A: [0,1,0,1,0,...]
206
+ B: [0,0,1,0,1,...] → B follows A (sync leak detected)
207
+ ```
175
208
 
176
- The tool records the last 50 "ticks" (roughly 1 second) for each state variable:
209
+ 3. **Lead-Lag (B A):** Does A consistently follow B?
177
210
  ```
178
- useState("count"): [0,1,0,0,1,1,0,0,0,...]
179
- useState("total"): [0,1,0,0,1,1,0,0,0,...]
180
- ↑ Both update at same times = probably redundant
211
+ A: [0,0,1,0,1,...]
212
+ B: [0,1,0,1,0,...] → A follows B (reverse causality)
181
213
  ```
182
214
 
183
- It uses **cosine similarity** (a standard correlation metric) to detect when states update together.
215
+ The engine uses **offset-based comparison** to check these patterns without allocating temporary arrays, ensuring minimal overhead even at high frame rates.
216
+
217
+ ### Performance Optimizations
184
218
 
185
- If similarity > 0.88, it flags them as potentially redundant.
219
+ - **Idle Filtering:** Only analyzes variables with 2+ updates, reducing pairwise comparisons by ~90% in typical applications (measured on Excalidraw's 47-hook codebase)
220
+ - **Batched Analysis:** Runs every 5 ticks (~100ms) to avoid impacting frame budget
221
+ - **Console Throttling:** Same alert won't repeat within 5 seconds
222
+ - **Zero Production Overhead:** Entire library is replaced with no-op shims in production builds
186
223
 
187
- ### Why This Works
224
+ ### What Gets Flagged?
188
225
 
189
- Most architectural issues create **temporal patterns**:
190
- - Redundant state → always updates together
191
- - Update chains one state updates, then another follows
192
- - Infinite loops same state updates rapidly
226
+ **Redundant Pattern Example:**
227
+ ```tsx
228
+ // Before (Basis flags this)
229
+ const [count, setCount] = useState(0);
230
+ const [doubled, setDoubled] = useState(0);
193
231
 
194
- The tool watches for these patterns and alerts you.
232
+ useEffect(() => {
233
+ setDoubled(count * 2);
234
+ }, [count]);
195
235
 
196
- ### What It Doesn't Do
236
+ // After (Basis suggestion)
237
+ const [count, setCount] = useState(0);
238
+ const doubled = useMemo(() => count * 2, [count]);
239
+ ```
197
240
 
198
- Doesn't analyze your code statically
199
- ❌ Doesn't read variable values
200
- Doesn't prove mathematical correctness
201
- Doesn't replace code review
241
+ **Sync Leak Example:**
242
+ ```tsx
243
+ // Before (causes double render)
244
+ const [user, setUser] = useState(null);
245
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
202
246
 
203
- It's a **diagnostic tool** - it points out patterns worth investigating.
247
+ useEffect(() => {
248
+ setIsLoggedIn(!!user);
249
+ }, [user]);
250
+
251
+ // ✅ After (single render)
252
+ const [user, setUser] = useState(null);
253
+ const isLoggedIn = !!user; // Derived, no effect needed
254
+ ```
204
255
 
205
256
  ---
206
257
 
207
258
  ## Production Safety
208
259
 
209
- In production builds, the entire tool is removed:
260
+ In production builds, the entire tool is replaced with zero-op shims. **Zero runtime overhead. Zero bundle size increase.**
210
261
  ```json
211
262
  // package.json - automatic based on NODE_ENV
212
263
  "exports": {
213
264
  ".": {
214
265
  "development": "./dist/index.mjs",
215
- "production": "./dist/production.mjs", // Zero-op shims
266
+ "production": "./dist/production.mjs", // No-op shims
216
267
  "default": "./dist/production.mjs"
217
268
  }
218
269
  }
219
270
  ```
220
271
 
221
- **Zero runtime overhead. Zero bundle size increase.**
272
+ ---
273
+
274
+ ## When to Skip Files
275
+
276
+ Add `// @basis-ignore` at the top of a file to disable instrumentation:
277
+ ```tsx
278
+ // @basis-ignore
279
+ // This file uses third-party library wrappers or hardware-bound synchronization that Basis shouldn't audit.
280
+ ```
281
+
282
+ **Good candidates for skipping:**
283
+ - **High-frequency animations** (>60fps state updates)
284
+ - **WebSocket message handlers** (rapid, intentional updates)
285
+ - **Canvas/WebGL render loops** (performance-critical paths)
286
+ - **Intentional synchronization** (e.g., React Query → local cache mirrors)
287
+ - **Third-party library wrappers** (where you don't control the architecture)
222
288
 
223
289
  ---
224
290
 
225
291
  ## Comparison to Other Tools
226
292
 
227
- | Tool | What It Detects | When |
228
- |------|----------------|------|
229
- | **React DevTools** | Component renders, props/state values | After the fact |
230
- | **Why Did You Render** | Unnecessary re-renders | During render |
231
- | **ESLint exhaustive-deps** | Missing effect dependencies | Static analysis |
232
- | **react-state-basis** | Redundant state, update chains, loops | Runtime patterns |
293
+ | Tool | Focus | When to Use |
294
+ |------|-------|-------------|
295
+ | **React DevTools** | Component hierarchy & values | Debugging specific components |
296
+ | **Why Did You Render** | Re-render optimization | Performance tuning renders |
297
+ | **ESLint exhaustive-deps** | Static dependency analysis | Preventing missing deps |
298
+ | **react-state-basis** | **Temporal state relationships** | **Finding redundant state & effect chains** |
233
299
 
234
- They're complementary - use them together.
300
+ These tools are complementary - use them together for best results.
235
301
 
236
302
  ---
237
303
 
238
- ## Skipping Files
304
+ ## Performance Impact (Measured)
239
305
 
240
- Add this comment to skip noisy files:
241
- ```tsx
242
- // @basis-ignore
243
- ```
306
+ **Development Mode**
244
307
 
245
- Good for:
246
- - Animation loops
247
- - High-frequency timers
248
- - WebSocket handlers
249
- - Performance-critical code
308
+ These measurements were taken using the built-in **Stress Lab** with 100 active hooks and continuous state updates, observed in Chrome DevTools Performance and Web Vitals panels.
250
309
 
251
- ---
310
+ Location: `/example`
252
311
 
253
- ## Limitations
312
+ **Observed impact:**
254
313
 
255
- ### Current Version (v0.3.x)
314
+ * **Per update overhead:** < 0.05ms (O(1) Map-based tracking)
315
+ * **Analysis pass:** ~1.0ms (Zero-copy pointer math)
316
+ * **Frame budget impact:** ~2% during active 100-hook stress testing
317
+ * **Latency (INP):** 80ms (v0.4.0) vs 464ms (v0.3.x legacy engine)
256
318
 
257
- **What works well:**
258
- - ✅ Detecting obvious redundant state
259
- - Flagging effect-driven update chains
260
- - Catching infinite loops
261
- - ✅ Works in typical React apps
319
+ > Results will vary by hardware, browser, and workload. Use the Stress Lab to reproduce and compare Basis ON vs OFF in your own environment.
320
+
321
+ <p align="center">
322
+ <img src="./assets/react-state-basis-stress.gif" width="800" alt="shadcn Admin Audit" />
323
+ </p>
262
324
 
263
- **Known issues:**
264
- - ⚠️ May miss delayed chains (>20ms apart)
265
- - ⚠️ Can flag intentional patterns as issues
266
- - ⚠️ Complex multi-way dependencies might not be caught
267
- - ⚠️ Requires judgment to interpret results
268
325
 
269
- **False positives happen.** Always verify before refactoring.
326
+ **Production Mode:**
327
+ - Overhead: ~0.01ms per hook call (negligible wrapper overhead)
328
+ - Bundle size: ~2-3 KB minified (no-op wrappers only, no analysis engine)
329
+ - Monitoring logic: 100% removed
330
+ - HUD component: 100% removed
270
331
 
271
332
  ---
272
333
 
273
- ## Roadmap
334
+ ## Limitations (v0.4.x)
274
335
 
275
- ### v0.4.0 (Next)
276
- - [ ] Zustand integration
277
- - [ ] Redux middleware
278
- - [ ] Better false positive filtering
279
- - [ ] Automated fix suggestions
336
+ **What works well:**
337
+ - Detecting synchronous redundant state
338
+ - Flagging effect-driven update chains (A → Effect → B)
339
+ - Catching infinite loops before browser freeze
340
+ - Distinguishing causality from redundancy
341
+
342
+ **Known edge cases:**
343
+ - ⚠️ **Async gaps:** Updates delayed by >40ms (e.g., slow API responses) may appear independent
344
+ - ⚠️ **Intentional sync:** Sometimes synchronization is required for library compatibility
345
+ - ⚠️ **Complex multi-way dependencies:** Three or more interconnected states might not show full relationship graph
346
+ - ⚠️ **Requires judgment:** Tool points out patterns worth investigating - you decide if they're issues
280
347
 
281
- ### v0.5.0 (Future)
282
- - [ ] Visual state dependency graph
283
- - [ ] Domain isolation analysis
284
- - [ ] Historical trend tracking
348
+ **Heuristic interpretation requires context. Always verify architectural intent before refactoring.**.
285
349
 
286
350
  ---
287
351
 
288
- ## Contributing
352
+ ## Roadmap
289
353
 
290
- Found a bug? Have an idea? Open an issue or PR.
354
+ ### v0.4.x
355
+ - [x] **v0.4.0**: Temporal Cross-Correlation Engine (Lead-Lag Analysis)
356
+ - [x] **v0.4.1:** Density Filtering (Eliminate false positives from animations/sliders)
357
+ - [ ] v0.4.2: Ring Buffer (Zero-jank memory management for 500+ hooks)
291
358
 
292
- For technical details on how the detection works, see the [Wiki](https://github.com/liovic/react-state-basis/wiki).
359
+ ### v0.5.0 (Planned)
360
+ - [ ] Zustand & Redux middleware integration
361
+ - [ ] Visual dependency graph in HUD
362
+ - [ ] Historical trend tracking across sessions
363
+
364
+ ### Future Ideas
365
+ - [ ] Domain isolation analysis (detect feature boundaries)
366
+ - [ ] Export audit reports for team reviews
367
+ - [ ] CI integration for architectural regressions
293
368
 
294
369
  ---
295
370
 
296
371
  ## FAQ
297
372
 
298
373
  **Q: Will this slow down my app?**
299
- A: Only in development. Production builds are zero-overhead.
374
+ A: Only in development (~0.3ms per update). Production builds have zero overhead.
300
375
 
301
376
  **Q: Do I have to change my code?**
302
377
  A: No. The Babel plugin instruments hooks automatically.
303
378
 
304
379
  **Q: What if it flags something that's not a problem?**
305
- A: Use your judgment. It's a diagnostic tool, not gospel.
380
+ A: Use your judgment. Basis is a diagnostic tool that points out patterns worth investigating - not all flagged patterns are bugs.
381
+
382
+ **Q: How is this different from Redux DevTools?**
383
+ A: Redux DevTools shows state values and action history. Basis shows temporal relationships between state variables, regardless of what state management library you use.
306
384
 
307
385
  **Q: Why "basis"?**
308
- A: In linear algebra, a "basis" is a minimal set of independent vectors. The name reflects the goal of finding your app's minimal independent state.
386
+ A: In linear algebra, a "basis" is a minimal set of independent vectors. The name reflects the goal of finding your app's minimal independent state - removing redundancy.
387
+
388
+ **Q: How is this different from Redux DevTools?**
389
+ A: Redux DevTools is a **Journal** - it logs specific values and actions within a Redux store. Basis is an **Architectural Auditor** - it instruments React's core primitives (useState, useReducer, useEffect) to detect hidden relationships between entirely separate components and hooks, even when they don't share a store. Redux DevTools answers "what changed and why?" while Basis answers "is this state architecture clean?"
390
+
391
+ ---
392
+
393
+ ## Contributing
394
+
395
+ Found a bug? Have an idea? Open an issue or PR.
396
+
397
+ For technical details on how the detection works, see the [Wiki](https://github.com/liovic/react-state-basis/wiki).
309
398
 
310
399
  ---
311
400