react-state-basis 0.6.3 → 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 +196 -75
- 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 +1 -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 visual overlay 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
|
|
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.
|
|
101
111
|
|
|
102
112
|
---
|
|
103
113
|
|
|
104
|
-
## What Basis
|
|
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.
|
|
105
123
|
|
|
106
|
-
|
|
124
|
+
### Correlated state
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
```
|
|
114
134
|
|
|
115
|
-
|
|
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.
|
|
116
160
|
|
|
117
161
|
---
|
|
118
162
|
|
|
119
|
-
|
|
163
|
+
### Important: these are signals, not proofs
|
|
120
164
|
|
|
121
|
-
|
|
122
|
-
Check your entire app's state architecture by running `window.printBasisReport()` in the console.
|
|
165
|
+
Basis uses runtime timing and correlation heuristics.
|
|
123
166
|
|
|
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 variables that tend to update together into clusters (e.g., boolean pairs that are really one piece of state).
|
|
167
|
+
A detected pattern is **not automatically a bug**, and Basis does not know the intent behind your application architecture.
|
|
127
168
|
|
|
128
|
-
|
|
129
|
-
|
|
169
|
+
Use the results as prompts for investigation rather than as rules for how React code should be written.
|
|
170
|
+
|
|
171
|
+
[See examples and possible fixes →](https://github.com/liovic/react-state-basis/wiki/The-Forensic-Catalog)
|
|
130
172
|
|
|
131
173
|
---
|
|
132
174
|
|
|
133
|
-
##
|
|
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:
|
|
134
186
|
|
|
135
|
-
|
|
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.
|
|
136
192
|
|
|
137
|
-
|
|
138
|
-
|
|
193
|
+
These metrics are diagnostic rather than a score for the quality of your application.
|
|
194
|
+
|
|
195
|
+
### Runtime metrics
|
|
196
|
+
|
|
197
|
+
You can inspect engine metrics with:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
window.getBasisMetrics()
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
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.
|
|
226
|
+
|
|
227
|
+
This can be useful for:
|
|
228
|
+
|
|
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 has been tested against industry-standard codebases:
|
|
|
143
237
|
|
|
144
238
|
### Zustand
|
|
145
239
|
|
|
146
|
-
|
|
147
|
-
store updates. Store signals appear in the HUD and health report alongside your React state.
|
|
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
|
-
**Global Event Fragmentation** across React and Zustand state simultaneously.
|
|
261
|
+
This allows React and Zustand updates to appear in the same runtime graph.
|
|
165
262
|
|
|
166
|
-
[See
|
|
263
|
+
[See the Zustand example →](./examples/basis-zustand/)
|
|
167
264
|
|
|
168
|
-
###
|
|
265
|
+
### Planned integrations
|
|
169
266
|
|
|
170
|
-
|
|
267
|
+
XState, React Query, and Redux Toolkit are planned.
|
|
268
|
+
|
|
269
|
+
Community contributions are welcome.
|
|
171
270
|
|
|
172
271
|
---
|
|
173
272
|
|
|
174
273
|
## Performance & Privacy
|
|
175
274
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
**
|
|
275
|
+
Basis is designed primarily as a development-time diagnostic tool.
|
|
276
|
+
|
|
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.
|
|
179
280
|
|
|
180
|
-
|
|
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.
|
|
185
290
|
|
|
186
|
-
|
|
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.
|
|
293
|
+
|
|
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
|
|
191
299
|
|
|
192
|
-
|
|
300
|
+
Basis observes the timing and relationships between state updates while your application runs.
|
|
193
301
|
|
|
194
|
-
|
|
195
|
-
✓ **v0.5.x** - Detect local copies of Context *(The Decomposition Era)*
|
|
196
|
-
→ **v0.6.x** - Rank which update fans out widest *(The Graph Era)*
|
|
197
|
-
**v0.7.x** - Detect derivative vs. independent state *(The Information Era)*
|
|
198
|
-
**v0.8.x** - Estimate how much local state a component actually needs *(The Manifold Era)*
|
|
302
|
+
It builds an in-memory representation of those updates and applies heuristics to identify recurring patterns.
|
|
199
303
|
|
|
304
|
+
It does **not** need to inspect the values stored in your state to perform these checks.
|
|
200
305
|
|
|
201
|
-
|
|
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.
|
|
307
|
+
|
|
308
|
+
For a deeper look at the implementation and underlying model:
|
|
309
|
+
|
|
310
|
+
[Read the documentation and theory →](https://github.com/liovic/react-state-basis/wiki)
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Roadmap
|
|
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
|
|
321
|
+
|
|
322
|
+
[See the full roadmap →](https://github.com/liovic/react-state-basis/wiki/Roadmap)
|
|
202
323
|
|
|
203
324
|
---
|
|
204
325
|
|
|
@@ -206,4 +327,4 @@ Each version answers a different architectural question:
|
|
|
206
327
|
|
|
207
328
|
Built by [LP](https://github.com/liovic) • [MIT License](https://opensource.org/licenses/MIT)
|
|
208
329
|
|
|
209
|
-
</div>
|
|
330
|
+
</div>
|
|
@@ -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
|