ccstate-react 4.13.0 → 5.2.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/coverage/base.css +224 -0
  3. package/coverage/block-navigation.js +87 -0
  4. package/coverage/clover.xml +219 -0
  5. package/coverage/coverage-final.json +8 -0
  6. package/coverage/favicon.png +0 -0
  7. package/coverage/index.html +206 -0
  8. package/coverage/index.ts.html +100 -0
  9. package/coverage/prettify.css +1 -0
  10. package/coverage/prettify.js +2 -0
  11. package/coverage/provider.ts.html +133 -0
  12. package/coverage/sort-arrow-sprite.png +0 -0
  13. package/coverage/sorter.js +196 -0
  14. package/coverage/useGet.ts.html +160 -0
  15. package/coverage/useLoadable.ts.html +403 -0
  16. package/coverage/useLoadableSet.ts.html +295 -0
  17. package/coverage/useResolved.ts.html +133 -0
  18. package/coverage/useSet.ts.html +166 -0
  19. package/dist/experimental.cjs +92 -45
  20. package/dist/experimental.d.cts +15 -6
  21. package/dist/experimental.d.ts +15 -6
  22. package/dist/experimental.js +93 -43
  23. package/dist/index.cjs +56 -125
  24. package/dist/index.d.cts +3 -3
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.js +57 -126
  27. package/package.json +9 -9
  28. package/src/__tests__/get-and-set.test.tsx +22 -13
  29. package/src/__tests__/loadable-set.test.tsx +255 -0
  30. package/src/__tests__/loadable.test.tsx +14 -6
  31. package/src/__tests__/resolved.test.tsx +6 -1
  32. package/src/experimental.ts +1 -1
  33. package/src/provider.ts +1 -2
  34. package/src/useGet.ts +19 -6
  35. package/src/useLoadable.ts +74 -95
  36. package/src/useLoadableSet.ts +72 -0
  37. package/src/useResolved.ts +2 -2
  38. package/LICENSE +0 -21
  39. package/src/__tests__/inline-atom.test.tsx +0 -139
  40. package/src/useInlineAtom.ts +0 -40
package/dist/index.js CHANGED
@@ -1,21 +1,30 @@
1
- import { createContext, useContext, useSyncExternalStore, useCallback, useState, useEffect, useRef } from 'react';
2
- import { getDefaultStore, command } from 'ccstate';
1
+ import { createContext, useContext, useRef, useSyncExternalStore, useCallback } from 'react';
3
2
 
4
3
  var StoreContext = createContext(null);
5
4
  var StoreProvider = StoreContext.Provider;
6
5
  function useStore() {
7
6
  var store = useContext(StoreContext);
8
7
  if (!store) {
9
- return getDefaultStore();
8
+ throw new Error('useStore must be used within a StoreProvider');
10
9
  }
11
10
  return store;
12
11
  }
13
12
 
14
13
  function useGet(atom) {
15
14
  var store = useStore();
16
- return useSyncExternalStore(function (fn) {
17
- return store.sub(atom, command(fn));
18
- }, function () {
15
+ var onChange = useRef(function (fn) {
16
+ var controller = new AbortController();
17
+ store.watch(function (get) {
18
+ get(atom);
19
+ fn();
20
+ }, {
21
+ signal: controller.signal
22
+ });
23
+ return function () {
24
+ controller.abort();
25
+ };
26
+ });
27
+ return useSyncExternalStore(onChange.current, function () {
19
28
  return store.get(atom);
20
29
  });
21
30
  }
@@ -32,132 +41,54 @@ function useSet(signal) {
32
41
  }, [store, signal]);
33
42
  }
34
43
 
35
- function _arrayLikeToArray(r, a) {
36
- (null == a || a > r.length) && (a = r.length);
37
- for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
38
- return n;
39
- }
40
- function _arrayWithHoles(r) {
41
- if (Array.isArray(r)) return r;
42
- }
43
- function _iterableToArrayLimit(r, l) {
44
- var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
45
- if (null != t) {
46
- var e,
47
- n,
48
- i,
49
- u,
50
- a = [],
51
- f = !0,
52
- o = !1;
53
- try {
54
- if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
55
- } catch (r) {
56
- o = !0, n = r;
57
- } finally {
58
- try {
59
- if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
60
- } finally {
61
- if (o) throw n;
62
- }
63
- }
64
- return a;
65
- }
66
- }
67
- function _nonIterableRest() {
68
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
69
- }
70
- function _slicedToArray(r, e) {
71
- return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
72
- }
73
- function _unsupportedIterableToArray(r, a) {
74
- if (r) {
75
- if ("string" == typeof r) return _arrayLikeToArray(r, a);
76
- var t = {}.toString.call(r).slice(8, -1);
77
- return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
78
- }
79
- }
80
-
81
- /**
82
- * Handles a specific behavior of useSyncExternalStore. In React, there are situations where the getSnapshot function of
83
- * useSyncExternalStore executes, but the Render function doesn't execute.
84
- *
85
- * This can cause the promise generated in that round to not be caught, and userspace has no opportunity to handle this
86
- * promise. Therefore, this issue needs to be handled in useGetPromise.
87
- *
88
- * @param atom
89
- * @returns
90
- */
91
- function useGetPromise(atom) {
92
- var store = useStore();
93
- var lastPromise = useRef(undefined);
94
- var promiseProcessed = useRef(false);
95
- var promise = useSyncExternalStore(function (fn) {
96
- return store.sub(atom, command(fn));
97
- }, function () {
98
- var val = store.get(atom);
99
-
100
- // If the last promise is not processed and the current value is a promise,
101
- // we need to silence the last promise to avoid unhandled rejections.
102
- if (lastPromise.current !== undefined && lastPromise.current !== val && !promiseProcessed.current) {
103
- lastPromise.current["catch"](function () {
104
- return void 0;
105
- });
106
- }
107
- if (lastPromise.current !== val) {
108
- promiseProcessed.current = false;
109
- lastPromise.current = val instanceof Promise ? val : undefined;
110
- }
111
- return val;
44
+ function useLoadableInternal(promise$, keepLastResolved) {
45
+ var promiseResult = useRef({
46
+ state: 'loading'
112
47
  });
113
- return [promise, function () {
114
- promiseProcessed.current = true;
115
- }];
116
- }
117
- function useLoadableInternal(atom, keepLastResolved) {
118
- var _useGetPromise = useGetPromise(atom),
119
- _useGetPromise2 = _slicedToArray(_useGetPromise, 2),
120
- promise = _useGetPromise2[0],
121
- setPromiseProcessed = _useGetPromise2[1];
122
- var _useState = useState({
123
- state: 'loading'
124
- }),
125
- _useState2 = _slicedToArray(_useState, 2),
126
- promiseResult = _useState2[0],
127
- setPromiseResult = _useState2[1];
128
- useEffect(function () {
129
- if (!(promise instanceof Promise)) {
130
- setPromiseResult({
131
- state: 'hasData',
132
- data: promise
133
- });
134
- return;
135
- }
136
- var cancelled = false;
137
- if (!keepLastResolved) {
138
- setPromiseResult({
139
- state: 'loading'
140
- });
48
+ var store = useStore();
49
+ var subStore = useCallback(function (fn) {
50
+ function updateResult(result, signal) {
51
+ if (signal.aborted) return;
52
+ promiseResult.current = result;
53
+ fn();
141
54
  }
142
- setPromiseProcessed();
143
- promise.then(function (ret) {
144
- if (cancelled) return;
145
- setPromiseResult({
146
- state: 'hasData',
147
- data: ret
148
- });
149
- }, function (error) {
150
- if (cancelled) return;
151
- setPromiseResult({
152
- state: 'hasError',
153
- error: error
55
+ var controller = new AbortController();
56
+ store.watch(function (get, _ref) {
57
+ var signal = _ref.signal;
58
+ var promise = get(promise$);
59
+ if (!(promise instanceof Promise)) {
60
+ updateResult({
61
+ state: 'hasData',
62
+ data: promise
63
+ }, signal);
64
+ return;
65
+ }
66
+ if (!keepLastResolved) {
67
+ updateResult({
68
+ state: 'loading'
69
+ }, signal);
70
+ }
71
+ promise.then(function (ret) {
72
+ updateResult({
73
+ state: 'hasData',
74
+ data: ret
75
+ }, signal);
76
+ }, function (error) {
77
+ updateResult({
78
+ state: 'hasError',
79
+ error: error
80
+ }, signal);
154
81
  });
82
+ }, {
83
+ signal: controller.signal
155
84
  });
156
85
  return function () {
157
- cancelled = true;
86
+ controller.abort();
158
87
  };
159
- }, [promise]);
160
- return promiseResult;
88
+ }, [store, promise$]);
89
+ return useSyncExternalStore(subStore, function () {
90
+ return promiseResult.current;
91
+ });
161
92
  }
162
93
  function useLoadable(atom) {
163
94
  return useLoadableInternal(atom, false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstate-react",
3
- "version": "4.13.0",
3
+ "version": "5.2.0",
4
4
  "description": "CCState React Hooks",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,6 +8,10 @@
8
8
  },
9
9
  "license": "MIT",
10
10
  "type": "module",
11
+ "scripts": {
12
+ "build": "rollup -c",
13
+ "prebuild": "shx rm -rf dist"
14
+ },
11
15
  "main": "./dist/index.cjs",
12
16
  "module": "./dist/index.js",
13
17
  "exports": {
@@ -25,7 +29,7 @@
25
29
  "react": ">=17.0.0"
26
30
  },
27
31
  "dependencies": {
28
- "ccstate": "^4.13.0"
32
+ "ccstate": "workspace:^"
29
33
  },
30
34
  "peerDependenciesMeta": {
31
35
  "@types/react": {
@@ -45,6 +49,7 @@
45
49
  "@testing-library/user-event": "^14.5.2",
46
50
  "@types/react": "^19.0.2",
47
51
  "@types/react-dom": "^18.3.1",
52
+ "ccstate": "workspace:^",
48
53
  "happy-dom": "^15.11.7",
49
54
  "jest-leak-detector": "^29.7.0",
50
55
  "json": "^11.0.0",
@@ -54,11 +59,6 @@
54
59
  "rollup-plugin-dts": "^6.1.1",
55
60
  "shx": "^0.3.4",
56
61
  "signal-timers": "^1.0.4",
57
- "vitest": "^2.1.8",
58
- "ccstate": "^4.13.0"
59
- },
60
- "scripts": {
61
- "build": "rollup -c",
62
- "prebuild": "shx rm -rf dist"
62
+ "vitest": "^2.1.8"
63
63
  }
64
- }
64
+ }
@@ -1,7 +1,7 @@
1
1
  import { render, cleanup, screen } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { afterEach, describe, expect, it, vi } from 'vitest';
4
- import { computed, createStore, command, state, createDebugStore, getDefaultStore } from 'ccstate';
4
+ import { computed, createStore, command, state, createDebugStore } from 'ccstate';
5
5
  import { StoreProvider, useGet, useSet } from '..';
6
6
  import { StrictMode, useState } from 'react';
7
7
  import '@testing-library/jest-dom/vitest';
@@ -198,21 +198,21 @@ describe('react', () => {
198
198
  expect(await screen.findByText('1')).toBeInTheDocument();
199
199
  });
200
200
 
201
- it('should use default store if no provider', () => {
201
+ it('throw error if no store provide', () => {
202
202
  const count$ = state(0);
203
- getDefaultStore().set(count$, 10);
204
203
 
205
204
  function App() {
206
205
  const count = useGet(count$);
207
206
  return <div>{count}</div>;
208
207
  }
209
208
 
210
- render(
211
- <StrictMode>
212
- <App />
213
- </StrictMode>,
214
- );
215
- expect(screen.getByText('10')).toBeInTheDocument();
209
+ expect(() => {
210
+ render(
211
+ <StrictMode>
212
+ <App />
213
+ </StrictMode>,
214
+ );
215
+ }).toThrowError('useStore must be used within a StoreProvider');
216
216
  });
217
217
 
218
218
  it('will unmount when component cleanup', async () => {
@@ -221,7 +221,7 @@ describe('react', () => {
221
221
 
222
222
  function App() {
223
223
  const ret = useGet(base$);
224
- return <div>{ret}</div>;
224
+ return <div>ret:{ret}</div>;
225
225
  }
226
226
 
227
227
  function Container() {
@@ -252,12 +252,16 @@ describe('react', () => {
252
252
  );
253
253
 
254
254
  const user = userEvent.setup();
255
- expect(store.getSubscribeGraph()).toHaveLength(1);
255
+
256
+ expect(screen.getByText('ret:0')).toBeInTheDocument();
256
257
  const button = screen.getByText('hide');
258
+
259
+ expect(store.getReadDependents(base$)).toHaveLength(2);
260
+
257
261
  expect(button).toBeInTheDocument();
258
262
  await user.click(button);
259
263
  expect(await screen.findByText('unmounted')).toBeInTheDocument();
260
- expect(store.getSubscribeGraph()).toHaveLength(0);
264
+ expect(store.getReadDependents(base$)).toHaveLength(1);
261
265
  });
262
266
  });
263
267
 
@@ -287,7 +291,12 @@ it('useSet should be stable', () => {
287
291
  return <div>Render</div>;
288
292
  }
289
293
 
290
- render(<Container />);
294
+ const store = createStore();
295
+ render(
296
+ <StoreProvider value={store}>
297
+ <Container />
298
+ </StoreProvider>,
299
+ );
291
300
 
292
301
  expect(trace).toHaveBeenCalledTimes(2);
293
302
  expect(trace.mock.calls[0][0]).toBe(trace.mock.calls[1][0]);
@@ -0,0 +1,255 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import '@testing-library/jest-dom/vitest';
4
+ import { render, cleanup, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import { afterEach, expect, it } from 'vitest';
7
+ import { delay } from 'signal-timers';
8
+ import { command, createStore, state } from 'ccstate';
9
+ import { StrictMode } from 'react';
10
+ import { StoreProvider } from '..';
11
+ import { useLoadableSet } from '../experimental';
12
+
13
+ afterEach(() => {
14
+ cleanup();
15
+ });
16
+
17
+ function makeDefered<T>(): {
18
+ resolve: (value: T) => void;
19
+ reject: (error: unknown) => void;
20
+ promise: Promise<T>;
21
+ } {
22
+ const deferred: {
23
+ resolve: (value: T) => void;
24
+ reject: (error: unknown) => void;
25
+ promise: Promise<T>;
26
+ } = {} as {
27
+ resolve: (value: T) => void;
28
+ reject: (error: unknown) => void;
29
+ promise: Promise<T>;
30
+ };
31
+
32
+ deferred.promise = new Promise((resolve, reject) => {
33
+ deferred.resolve = resolve;
34
+ deferred.reject = reject;
35
+ });
36
+
37
+ return deferred;
38
+ }
39
+
40
+ it('convert a async command to loadable', async () => {
41
+ const deferred = makeDefered<string>();
42
+ const setFoo$ = command(async () => {
43
+ return await deferred.promise;
44
+ });
45
+ const App = () => {
46
+ const [ret, setFoo] = useLoadableSet(setFoo$);
47
+ if (ret.state === 'loading') {
48
+ return <div>loading</div>;
49
+ } else if (ret.state === 'hasData') {
50
+ return <div>{ret.data}</div>;
51
+ }
52
+ return (
53
+ <button
54
+ onClick={() => {
55
+ void setFoo();
56
+ }}
57
+ >
58
+ Click
59
+ </button>
60
+ );
61
+ };
62
+ const store = createStore();
63
+ render(
64
+ <StoreProvider value={store}>
65
+ <App />
66
+ </StoreProvider>,
67
+ { wrapper: StrictMode },
68
+ );
69
+
70
+ expect(screen.getByText('Click')).toBeTruthy();
71
+
72
+ const btn = await screen.findByText('Click');
73
+ await userEvent.click(btn);
74
+
75
+ expect(await screen.findByText('loading')).toBeTruthy();
76
+
77
+ deferred.resolve('foo');
78
+ expect(await screen.findByText('foo')).toBeTruthy();
79
+ });
80
+
81
+ it('async command reject turns into hasError', async () => {
82
+ const deferred = makeDefered<string>();
83
+ const setFoo$ = command(async () => {
84
+ return await deferred.promise;
85
+ });
86
+ const App = () => {
87
+ const [ret, setFoo] = useLoadableSet(setFoo$);
88
+ if (ret.state === 'loading') {
89
+ return <div>loading</div>;
90
+ } else if (ret.state === 'hasError') {
91
+ return <div>{String(ret.error)}</div>;
92
+ }
93
+ return (
94
+ <button
95
+ onClick={() => {
96
+ void setFoo();
97
+ }}
98
+ >
99
+ Click
100
+ </button>
101
+ );
102
+ };
103
+ const store = createStore();
104
+ render(
105
+ <StoreProvider value={store}>
106
+ <App />
107
+ </StoreProvider>,
108
+ { wrapper: StrictMode },
109
+ );
110
+
111
+ await userEvent.click(await screen.findByText('Click'));
112
+ expect(await screen.findByText('loading')).toBeTruthy();
113
+
114
+ deferred.reject(new Error('oops'));
115
+ expect(await screen.findByText('Error: oops')).toBeTruthy();
116
+ });
117
+
118
+ it('sync command resolves immediately to hasData', async () => {
119
+ const setFoo$ = command(() => 42);
120
+ const App = () => {
121
+ const [ret, setFoo] = useLoadableSet(setFoo$);
122
+ if (ret.state === 'hasData') {
123
+ return <div>{String(ret.data)}</div>;
124
+ }
125
+ return (
126
+ <button
127
+ onClick={() => {
128
+ void setFoo();
129
+ }}
130
+ >
131
+ Click
132
+ </button>
133
+ );
134
+ };
135
+ const store = createStore();
136
+ render(
137
+ <StoreProvider value={store}>
138
+ <App />
139
+ </StoreProvider>,
140
+ { wrapper: StrictMode },
141
+ );
142
+
143
+ await userEvent.click(await screen.findByText('Click'));
144
+ expect(await screen.findByText('42')).toBeTruthy();
145
+ });
146
+
147
+ it('state atom setter transitions to hasData', async () => {
148
+ const count$ = state(0);
149
+ const App = () => {
150
+ const [ret, setCount] = useLoadableSet(count$);
151
+ if (ret.state === 'hasData') {
152
+ return <div>done</div>;
153
+ }
154
+ return (
155
+ <button
156
+ onClick={() => {
157
+ setCount(1);
158
+ }}
159
+ >
160
+ Click
161
+ </button>
162
+ );
163
+ };
164
+ const store = createStore();
165
+ render(
166
+ <StoreProvider value={store}>
167
+ <App />
168
+ </StoreProvider>,
169
+ { wrapper: StrictMode },
170
+ );
171
+
172
+ await userEvent.click(await screen.findByText('Click'));
173
+ expect(await screen.findByText('done')).toBeTruthy();
174
+ });
175
+
176
+ it('second invoke cancels first pending promise', async () => {
177
+ const deferred1 = makeDefered<string>();
178
+ const deferred2 = makeDefered<string>();
179
+ let counter = 0;
180
+
181
+ const setFoo$ = command(async () => {
182
+ const deferred = counter === 0 ? deferred1 : deferred2;
183
+ counter++;
184
+ return await deferred.promise;
185
+ });
186
+
187
+ const App = () => {
188
+ const [ret, setFoo] = useLoadableSet(setFoo$);
189
+ if (ret.state === 'hasData') {
190
+ return <div>data:{String(ret.data)}</div>;
191
+ }
192
+ return (
193
+ <button
194
+ onClick={() => {
195
+ void setFoo();
196
+ }}
197
+ >
198
+ Click
199
+ </button>
200
+ );
201
+ };
202
+
203
+ const store = createStore();
204
+ render(
205
+ <StoreProvider value={store}>
206
+ <App />
207
+ </StoreProvider>,
208
+ { wrapper: StrictMode },
209
+ );
210
+
211
+ await userEvent.click(await screen.findByText('Click'));
212
+ // button still visible during loading — second invoke aborts the first
213
+ await userEvent.click(await screen.findByText('Click'));
214
+
215
+ deferred2.resolve('second');
216
+ expect(await screen.findByText('data:second')).toBeTruthy();
217
+
218
+ deferred1.resolve('first');
219
+ await delay(0);
220
+ expect(screen.queryByText('data:first')).toBeNull();
221
+ });
222
+
223
+ it('invoke return value is identical to command return value', async () => {
224
+ const deferred = makeDefered<string>();
225
+ const setFoo$ = command(async () => {
226
+ return await deferred.promise;
227
+ });
228
+
229
+ let invokeResult: Promise<string> | undefined;
230
+
231
+ const App = () => {
232
+ const [, setFoo] = useLoadableSet(setFoo$);
233
+ return (
234
+ <button
235
+ onClick={() => {
236
+ invokeResult = setFoo();
237
+ }}
238
+ >
239
+ Click
240
+ </button>
241
+ );
242
+ };
243
+
244
+ const store = createStore();
245
+ render(
246
+ <StoreProvider value={store}>
247
+ <App />
248
+ </StoreProvider>,
249
+ { wrapper: StrictMode },
250
+ );
251
+
252
+ await userEvent.click(await screen.findByText('Click'));
253
+ deferred.resolve('foo');
254
+ expect(await invokeResult).toBe('foo');
255
+ });
@@ -539,7 +539,12 @@ it('useLoadable accept sync computed', async () => {
539
539
  return <div>{base.state}</div>;
540
540
  }
541
541
 
542
- render(<App />);
542
+ const store = createStore();
543
+ render(
544
+ <StoreProvider value={store}>
545
+ <App />
546
+ </StoreProvider>,
547
+ );
543
548
 
544
549
  expect(await screen.findByText('hasData')).toBeInTheDocument();
545
550
  });
@@ -605,10 +610,10 @@ test('useLoadable should catch errors', () => {
605
610
  get(reload$);
606
611
 
607
612
  const p = Promise.resolve();
608
- const originalCatch = p.catch.bind(p);
609
- vi.spyOn(p, 'catch').mockImplementation((...args) => {
613
+ const originalThen = p.then.bind(p);
614
+ vi.spyOn(p, 'then').mockImplementation((...args) => {
610
615
  traceCatch();
611
- return originalCatch(...args);
616
+ return originalThen(...args);
612
617
  });
613
618
  return p;
614
619
  });
@@ -628,9 +633,12 @@ test('useLoadable should catch errors', () => {
628
633
  </StoreProvider>
629
634
  </StrictMode>,
630
635
  );
636
+ expect(traceCatch).toHaveBeenCalledTimes(2); // strict mode renders twice
631
637
 
632
638
  store.set(reload$, (x) => x + 1);
633
- store.set(reload$, (x) => x + 1);
639
+ expect(traceCatch).toHaveBeenCalledTimes(3);
634
640
 
635
- expect(traceCatch).toHaveBeenCalledTimes(1);
641
+ store.set(reload$, (x) => x + 1);
642
+ store.set(reload$, (x) => x + 1);
643
+ expect(traceCatch).toHaveBeenCalledTimes(5);
636
644
  });
@@ -109,7 +109,12 @@ it('useResolved accept sync computed', async () => {
109
109
  return <div>{base}</div>;
110
110
  }
111
111
 
112
- render(<App />);
112
+ const store = createStore();
113
+ render(
114
+ <StoreProvider value={store}>
115
+ <App />
116
+ </StoreProvider>,
117
+ );
113
118
 
114
119
  expect(await screen.findByText('0')).toBeInTheDocument();
115
120
  });
@@ -1 +1 @@
1
- export { useCCState, useComputed, useCommand, useSub } from './useInlineAtom';
1
+ export { useLoadableSet } from './useLoadableSet';
package/src/provider.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { createContext, useContext } from 'react';
2
- import { getDefaultStore } from 'ccstate';
3
2
  import type { Store } from 'ccstate';
4
3
 
5
4
  const StoreContext = createContext<Store | null>(null);
@@ -10,7 +9,7 @@ export function useStore(): Store {
10
9
  const store = useContext(StoreContext);
11
10
 
12
11
  if (!store) {
13
- return getDefaultStore();
12
+ throw new Error('useStore must be used within a StoreProvider');
14
13
  }
15
14
 
16
15
  return store;