context-scoped-state 0.0.10 → 0.0.12

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx as C } from "react/jsx-runtime";
2
- import b, { useSyncExternalStore as H } from "react";
2
+ import h, { useSyncExternalStore as H } from "react";
3
3
  var m = function(e, r) {
4
4
  return m = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(t, n) {
5
5
  t.__proto__ = n;
@@ -7,7 +7,7 @@ var m = function(e, r) {
7
7
  for (var o in n) Object.prototype.hasOwnProperty.call(n, o) && (t[o] = n[o]);
8
8
  }, m(e, r);
9
9
  };
10
- function p(e, r) {
10
+ function v(e, r) {
11
11
  if (typeof r != "function" && r !== null)
12
12
  throw new TypeError("Class extends value " + String(r) + " is not a constructor or null");
13
13
  m(e, r);
@@ -85,8 +85,8 @@ var S = (function() {
85
85
  if (this._parentage = null, Array.isArray(s))
86
86
  try {
87
87
  for (var u = w(s), c = u.next(); !c.done; c = u.next()) {
88
- var h = c.value;
89
- h.remove(this);
88
+ var f = c.value;
89
+ f.remove(this);
90
90
  }
91
91
  } catch (a) {
92
92
  r = { error: a };
@@ -110,8 +110,8 @@ var S = (function() {
110
110
  if (P) {
111
111
  this._finalizers = null;
112
112
  try {
113
- for (var v = w(P), f = v.next(); !f.done; f = v.next()) {
114
- var V = f.value;
113
+ for (var b = w(P), p = b.next(); !p.done; p = b.next()) {
114
+ var V = p.value;
115
115
  try {
116
116
  A(V);
117
117
  } catch (a) {
@@ -122,7 +122,7 @@ var S = (function() {
122
122
  n = { error: a };
123
123
  } finally {
124
124
  try {
125
- f && !f.done && (o = v.return) && o.call(v);
125
+ p && !p.done && (o = b.return) && o.call(b);
126
126
  } finally {
127
127
  if (n) throw n.error;
128
128
  }
@@ -169,7 +169,7 @@ function A(e) {
169
169
  }
170
170
  var $ = {
171
171
  Promise: void 0
172
- }, q = {
172
+ }, W = {
173
173
  setTimeout: function(e, r) {
174
174
  for (var t = [], n = 2; n < arguments.length; n++)
175
175
  t[n - 2] = arguments[n];
@@ -180,21 +180,21 @@ var $ = {
180
180
  },
181
181
  delegate: void 0
182
182
  };
183
- function D(e) {
184
- q.setTimeout(function() {
183
+ function q(e) {
184
+ W.setTimeout(function() {
185
185
  throw e;
186
186
  });
187
187
  }
188
- function I() {
188
+ function T() {
189
189
  }
190
190
  function y(e) {
191
191
  e();
192
192
  }
193
193
  var F = (function(e) {
194
- p(r, e);
194
+ v(r, e);
195
195
  function r(t) {
196
196
  var n = e.call(this) || this;
197
- return n.isStopped = !1, t ? (n.destination = t, R(t) && t.add(n)) : n.destination = K, n;
197
+ return n.isStopped = !1, t ? (n.destination = t, R(t) && t.add(n)) : n.destination = J, n;
198
198
  }
199
199
  return r.create = function(t, n, o) {
200
200
  return new O(t, n, o);
@@ -221,7 +221,7 @@ var F = (function(e) {
221
221
  this.unsubscribe();
222
222
  }
223
223
  }, r;
224
- })(S), G = (function() {
224
+ })(S), D = (function() {
225
225
  function e(r) {
226
226
  this.partialObserver = r;
227
227
  }
@@ -253,42 +253,42 @@ var F = (function(e) {
253
253
  }
254
254
  }, e;
255
255
  })(), O = (function(e) {
256
- p(r, e);
256
+ v(r, e);
257
257
  function r(t, n, o) {
258
258
  var i = e.call(this) || this, s;
259
259
  return l(t) || !t ? s = {
260
260
  next: t ?? void 0,
261
261
  error: n ?? void 0,
262
262
  complete: o ?? void 0
263
- } : s = t, i.destination = new G(s), i;
263
+ } : s = t, i.destination = new D(s), i;
264
264
  }
265
265
  return r;
266
266
  })(F);
267
267
  function d(e) {
268
- D(e);
268
+ q(e);
269
269
  }
270
- function J(e) {
270
+ function G(e) {
271
271
  throw e;
272
272
  }
273
- var K = {
273
+ var J = {
274
274
  closed: !0,
275
- next: I,
276
- error: J,
277
- complete: I
278
- }, L = (function() {
275
+ next: T,
276
+ error: G,
277
+ complete: T
278
+ }, K = (function() {
279
279
  return typeof Symbol == "function" && Symbol.observable || "@@observable";
280
280
  })();
281
- function Q(e) {
281
+ function L(e) {
282
282
  return e;
283
283
  }
284
- function W(e) {
285
- return e.length === 0 ? Q : e.length === 1 ? e[0] : function(t) {
284
+ function Q(e) {
285
+ return e.length === 0 ? L : e.length === 1 ? e[0] : function(t) {
286
286
  return e.reduce(function(n, o) {
287
287
  return o(n);
288
288
  }, t);
289
289
  };
290
290
  }
291
- var T = (function() {
291
+ var I = (function() {
292
292
  function e(r) {
293
293
  r && (this._subscribe = r);
294
294
  }
@@ -326,12 +326,12 @@ var T = (function() {
326
326
  }, e.prototype._subscribe = function(r) {
327
327
  var t;
328
328
  return (t = this.source) === null || t === void 0 ? void 0 : t.subscribe(r);
329
- }, e.prototype[L] = function() {
329
+ }, e.prototype[K] = function() {
330
330
  return this;
331
331
  }, e.prototype.pipe = function() {
332
332
  for (var r = [], t = 0; t < arguments.length; t++)
333
333
  r[t] = arguments[t];
334
- return W(r)(this);
334
+ return Q(r)(this);
335
335
  }, e.prototype.toPromise = function(r) {
336
336
  var t = this;
337
337
  return r = k(r), new r(function(n, o) {
@@ -363,7 +363,7 @@ var z = M(function(e) {
363
363
  e(this), this.name = "ObjectUnsubscribedError", this.message = "object unsubscribed";
364
364
  };
365
365
  }), B = (function(e) {
366
- p(r, e);
366
+ v(r, e);
367
367
  function r() {
368
368
  var t = e.call(this) || this;
369
369
  return t.closed = !1, t.currentObservers = null, t.observers = [], t.isStopped = !1, t.hasError = !1, t.thrownError = null, t;
@@ -385,8 +385,8 @@ var z = M(function(e) {
385
385
  var c = u.value;
386
386
  c.next(t);
387
387
  }
388
- } catch (h) {
389
- o = { error: h };
388
+ } catch (f) {
389
+ o = { error: f };
390
390
  } finally {
391
391
  try {
392
392
  u && !u.done && (i = s.return) && i.call(s);
@@ -436,13 +436,13 @@ var z = M(function(e) {
436
436
  var n = this, o = n.hasError, i = n.thrownError, s = n.isStopped;
437
437
  o ? t.error(i) : s && t.complete();
438
438
  }, r.prototype.asObservable = function() {
439
- var t = new T();
439
+ var t = new I();
440
440
  return t.source = this, t;
441
441
  }, r.create = function(t, n) {
442
442
  return new U(t, n);
443
443
  }, r;
444
- })(T), U = (function(e) {
445
- p(r, e);
444
+ })(I), U = (function(e) {
445
+ v(r, e);
446
446
  function r(t, n) {
447
447
  var o = e.call(this) || this;
448
448
  return o.destination = t, o.source = n, o;
@@ -461,7 +461,7 @@ var z = M(function(e) {
461
461
  return (o = (n = this.source) === null || n === void 0 ? void 0 : n.subscribe(t)) !== null && o !== void 0 ? o : Y;
462
462
  }, r;
463
463
  })(B), N = (function(e) {
464
- p(r, e);
464
+ v(r, e);
465
465
  function r(t) {
466
466
  var n = e.call(this) || this;
467
467
  return n._value = t, n;
@@ -486,8 +486,10 @@ var z = M(function(e) {
486
486
  })(B);
487
487
  class et {
488
488
  _stateSubject;
489
- constructor() {
490
- this.state = this.getInitialState(), this._stateSubject = new N(this.getInitialState());
489
+ constructor(r) {
490
+ this._stateSubject = new N(
491
+ this.getInitialState(r)
492
+ );
491
493
  }
492
494
  getState() {
493
495
  return this._stateSubject.value;
@@ -495,7 +497,6 @@ class et {
495
497
  state$() {
496
498
  return this._stateSubject.asObservable();
497
499
  }
498
- state;
499
500
  setState(r) {
500
501
  const t = typeof r == "function" ? r(this._stateSubject.value) : r;
501
502
  this._stateSubject.next(t);
@@ -506,10 +507,14 @@ class et {
506
507
  }
507
508
  }
508
509
  function nt(e) {
509
- const r = b.createContext(void 0);
510
- function t() {
511
- const n = b.useContext(r);
512
- if (!n)
510
+ const r = class extends e {
511
+ state;
512
+ }, t = h.createContext(
513
+ void 0
514
+ );
515
+ function n() {
516
+ const o = h.useContext(t);
517
+ if (!o)
513
518
  throw new Error(
514
519
  `Store hook used outside of its Context provider.
515
520
 
@@ -521,38 +526,42 @@ Then wrap your component with:
521
526
  <YourComponent />
522
527
  </useYourStore.Context>`
523
528
  );
524
- const o = H(
525
- (i) => {
526
- const s = n.state$().subscribe(i);
527
- return () => s.unsubscribe();
529
+ const i = h.useCallback(
530
+ (u) => {
531
+ const c = o.state$().subscribe(u);
532
+ return () => c.unsubscribe();
528
533
  },
529
- () => n.getState(),
530
- () => n.getState()
534
+ [o]
535
+ ), s = H(
536
+ i,
537
+ () => o.getState(),
538
+ () => o.getState()
531
539
  // getServerSnapshot for SSR
532
540
  );
533
- return n.state = o, n;
541
+ return o.state = s, o;
534
542
  }
535
- return t.Context = function({
536
- children: o
543
+ return n.Context = function({
544
+ children: i,
545
+ value: s
537
546
  }) {
538
- const [i] = b.useState(() => new e());
539
- return /* @__PURE__ */ C(r.Provider, { value: i, children: o });
540
- }, t.MockContext = function({
541
- children: o,
542
- state: i
547
+ const [u] = h.useState(() => new r(s));
548
+ return /* @__PURE__ */ C(t.Provider, { value: u, children: i });
549
+ }, n.MockContext = function({
550
+ children: i,
551
+ state: s
543
552
  }) {
544
- const s = () => {
545
- if (i === void 0)
546
- return new e();
547
- class c extends e {
553
+ const u = () => {
554
+ if (s === void 0)
555
+ return new r();
556
+ const f = class extends r {
548
557
  getInitialState() {
549
- return i;
558
+ return s;
550
559
  }
551
- }
552
- return new c();
553
- }, u = b.useRef(s());
554
- return /* @__PURE__ */ C(r.Provider, { value: u.current, children: o });
555
- }, t;
560
+ };
561
+ return new f();
562
+ }, c = h.useRef(u());
563
+ return /* @__PURE__ */ C(t.Provider, { value: c.current, children: i });
564
+ }, n;
556
565
  }
557
566
  export {
558
567
  et as Store,
@@ -1,10 +1,9 @@
1
- declare abstract class Store<T> {
2
- protected abstract getInitialState(): T;
1
+ declare abstract class Store<T, C = Partial<T>> {
2
+ protected abstract getInitialState(contextValue?: C): T;
3
3
  private readonly _stateSubject;
4
- constructor();
4
+ constructor(contextValue?: C);
5
5
  getState(): T;
6
6
  state$(): import('rxjs').Observable<T>;
7
- state: T;
8
7
  protected setState(newState: T | ((currentState: T) => T)): void;
9
8
  protected patchState(partialState: Partial<T> | ((currentState: T) => Partial<T>)): void;
10
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Store.d.ts","sourceRoot":"","sources":["../../src/lib/Store.ts"],"names":[],"mappings":"AAEA,uBAAe,KAAK,CAAC,CAAC;IACpB,SAAS,CAAC,QAAQ,CAAC,eAAe,IAAI,CAAC;IAEvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;;IAOnD,QAAQ,IAAI,CAAC;IAIb,MAAM;IAIC,KAAK,EAAE,CAAC,CAAC;IAEhB,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI;IAQhE,SAAS,CAAC,UAAU,CAClB,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAC3D,IAAI;CAQR;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"Store.d.ts","sourceRoot":"","sources":["../../src/lib/Store.ts"],"names":[],"mappings":"AAEA,uBAAe,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IACpC,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC;IAEvD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;gBAEvC,YAAY,CAAC,EAAE,CAAC;IAM5B,QAAQ,IAAI,CAAC;IAIb,MAAM;IAIN,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI;IAQhE,SAAS,CAAC,UAAU,CAClB,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAC3D,IAAI;CAQR;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -1,9 +1,12 @@
1
1
  import { default as React } from 'react';
2
2
  import { Store } from './Store';
3
- declare function createStoreHook<T extends Store<any>>(storeClass: new () => T): {
4
- (): T;
5
- Context({ children, }: {
3
+ declare function createStoreHook<T extends Store<any, any>>(storeClass: new (contextValue?: any) => T): {
4
+ (): T & {
5
+ readonly state: ReturnType<T["getState"]>;
6
+ };
7
+ Context({ children, value, }: {
6
8
  children: React.ReactNode;
9
+ value?: T extends Store<any, infer C> ? C : never;
7
10
  }): import("react/jsx-runtime").JSX.Element;
8
11
  MockContext({ children, state, }: {
9
12
  children: React.ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"createStore.d.ts","sourceRoot":"","sources":["../../src/lib/createStore.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,iBAAS,eAAe,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC;QAG/C,CAAC;2BA+BnB;QACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;KAC3B;sCAWE;QACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;QAC1B,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;KACnC;EAuBF;AACD,OAAO,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"createStore.d.ts","sourceRoot":"","sources":["../../src/lib/createStore.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,iBAAS,eAAe,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,EAChD,UAAU,EAAE,KAAK,YAAY,CAAC,EAAE,GAAG,KAAK,CAAC;;;;kCAyDtC;QACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;QAC1B,KAAK,CAAC,6BAvD2C,CAAC,cAuDzB;KAC1B;sCAWE;QACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;QAC1B,KAAK,CAAC,4BAAY;KACnB;EAwBF;AACD,OAAO,EAAE,eAAe,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-scoped-state",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
package/README.md DELETED
@@ -1,233 +0,0 @@
1
- <div align="center">
2
- <img src="logo.svg" alt="context-scoped-state logo" width="120" height="120">
3
- <h1>context-scoped-state</h1>
4
- <p><strong>State management that respects component boundaries.</strong></p>
5
- </div>
6
-
7
- Unlike global state libraries (Redux, Zustand), `context-scoped-state` keeps your state where it belongs — scoped to the component tree that needs it. Each context provider creates an independent store instance, making your components truly reusable and your tests truly isolated.
8
-
9
- ## Why Scoped State?
10
-
11
- Global state is convenient, but it comes with hidden costs:
12
-
13
- - **Testing nightmares** — State leaks between tests, requiring complex cleanup
14
- - **Component coupling** — Reusing components means sharing their global state
15
- - **Implicit dependencies** — Components magically depend on global singletons
16
-
17
- `context-scoped-state` solves this by leveraging React's Context API the right way. Same API simplicity, but with proper encapsulation.
18
-
19
- ## Installation
20
-
21
- ```bash
22
- npm install context-scoped-state
23
- ```
24
-
25
- ```bash
26
- yarn add context-scoped-state
27
- ```
28
-
29
- ```bash
30
- pnpm add context-scoped-state
31
- ```
32
-
33
- > **Peer Dependencies:** React 18+
34
-
35
- ## Try it Online
36
-
37
- [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/HarshRohila/context-scoped-state/tree/master/examples/playground)
38
-
39
- ## Quick Start
40
-
41
- ### 1. Create Your Store (one file, one export)
42
-
43
- ```tsx
44
- // counterStore.ts
45
- import { Store, createStoreHook } from 'context-scoped-state';
46
-
47
- class CounterStore extends Store<{ count: number }> {
48
- protected getInitialState() {
49
- return { count: 0 };
50
- }
51
-
52
- increment() {
53
- // Callback-based: receives current state, returns new state
54
- this.setState(state => ({ count: state.count + 1 }));
55
- }
56
-
57
- decrement() {
58
- // Direct value: pass the new state directly
59
- this.setState({ count: this.getState().count - 1 });
60
- }
61
- }
62
-
63
- // This single export is all you need
64
- export const useCounterStore = createStoreHook(CounterStore);
65
- ```
66
-
67
- ### 2. Use in Your App
68
-
69
- ```tsx
70
- import { useCounterStore } from './counterStore';
71
-
72
- function Counter() {
73
- const counterStore = useCounterStore();
74
-
75
- return (
76
- <div>
77
- <span>{counterStore.state.count}</span>
78
- <button onClick={() => counterStore.increment()}>+</button>
79
- <button onClick={() => counterStore.decrement()}>-</button>
80
- </div>
81
- );
82
- }
83
-
84
- function App() {
85
- return (
86
- <useCounterStore.Context>
87
- <Counter />
88
- </useCounterStore.Context>
89
- );
90
- }
91
- ```
92
-
93
- That's it. One hook export gives you the hook and its `.Context` provider. No extra setup needed.
94
-
95
- ### Partial State Updates with patchState
96
-
97
- For stores with multiple properties, use `patchState` to update only specific fields:
98
-
99
- ```tsx
100
- class UserStore extends Store<{ name: string; age: number; email: string }> {
101
- protected getInitialState() {
102
- return { name: '', age: 0, email: '' };
103
- }
104
-
105
- updateName(name: string) {
106
- // Only updates name, preserves age and email
107
- this.patchState({ name });
108
- }
109
-
110
- incrementAge() {
111
- // Callback-based: receives current state, returns partial update
112
- this.patchState(state => ({ age: state.age + 1 }));
113
- }
114
- }
115
- ```
116
-
117
- - `setState` — Replaces the entire state
118
- - `patchState` — Merges partial updates into existing state
119
-
120
- ## Examples
121
-
122
- ### Independent Nested Stores
123
-
124
- Each `Context` creates a completely independent store instance. Perfect for reusable widget patterns:
125
-
126
- ```tsx
127
- function PlayerScore() {
128
- const store = useScoreStore();
129
- return <span>Score: {store.state.score}</span>;
130
- }
131
-
132
- function Game() {
133
- return (
134
- <div>
135
- {/* Player 1 has their own score */}
136
- <useScoreStore.Context>
137
- <h2>Player 1</h2>
138
- <PlayerScore />
139
- </useScoreStore.Context>
140
-
141
- {/* Player 2 has their own score */}
142
- <useScoreStore.Context>
143
- <h2>Player 2</h2>
144
- <PlayerScore />
145
- </useScoreStore.Context>
146
- </div>
147
- );
148
- }
149
- ```
150
-
151
- Both players have completely independent state — no configuration needed.
152
-
153
- ### Testing with MockContext
154
-
155
- Test components in any state without complex setup:
156
-
157
- ```tsx
158
- import { render, screen } from '@testing-library/react';
159
-
160
- test('shows warning when balance is low', () => {
161
- render(
162
- <useAccountStore.MockContext state={{ balance: 5, currency: 'USD' }}>
163
- <AccountStatus />
164
- </useAccountStore.MockContext>,
165
- );
166
-
167
- expect(screen.getByText('Low balance warning')).toBeInTheDocument();
168
- });
169
-
170
- test('shows normal status when balance is healthy', () => {
171
- render(
172
- <useAccountStore.MockContext state={{ balance: 1000, currency: 'USD' }}>
173
- <AccountStatus />
174
- </useAccountStore.MockContext>,
175
- );
176
-
177
- expect(screen.queryByText('Low balance warning')).not.toBeInTheDocument();
178
- });
179
- ```
180
-
181
- No mocking libraries. No global state cleanup. Just render with the state you need.
182
-
183
- ## Why context-scoped-state Over Other Libraries?
184
-
185
- | Feature | context-scoped-state | Redux | Zustand |
186
- | ---------------------- | -------------------- | ---------------------------- | -------------- |
187
- | **Scoped by default** | Yes | No | No |
188
- | **Multiple instances** | Automatic | Manual wiring | Manual wiring |
189
- | **Test isolation** | Built-in MockContext | Requires setup | Requires reset |
190
- | **Boilerplate** | Low | High | Low |
191
- | **Type safety** | Full | Requires setup | Good |
192
- | **Learning curve** | Just classes | Actions, reducers, selectors | Simple |
193
-
194
- ### The Core Difference
195
-
196
- **Global state libraries** make you fight against React's component model. You end up with:
197
-
198
- - Selector functions to prevent re-renders
199
- - Complex test fixtures to reset global state
200
- - Workarounds for component reusability
201
-
202
- **context-scoped-state** works _with_ React:
203
-
204
- - State lives in the component tree, just like React intended
205
- - Each provider = new instance, automatically
206
- - Testing is just rendering with different props
207
-
208
- ### When to Use What
209
-
210
- **Use context-scoped-state when:**
211
-
212
- - Building reusable components with internal state
213
- - You want test isolation without extra setup
214
- - State naturally belongs to a subtree, not the whole app
215
-
216
- **Need global state?** Just place the Context at your app root — same API, app-wide access.
217
-
218
- ### Why Not Just Use useState or useReducer?
219
-
220
- **vs useState:**
221
-
222
- - `useState` binds state directly to the component — poor separation of concerns and hard to test since you can't easily set a component to a specific state
223
- - Lifting state up with `useState` requires refactoring components and passing props; with `context-scoped-state`, just move the Context wrapper up the tree
224
-
225
- **vs useReducer:**
226
-
227
- - No action types, switch statements, or dispatch boilerplate
228
- - Just call methods directly: `store.increment()` instead of `dispatch({ type: 'INCREMENT' })`
229
- - Full TypeScript autocomplete for your actions
230
-
231
- ---
232
-
233
- **context-scoped-state** — Because not all state needs to be global.