ccstate-react 3.0.0 → 4.1.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.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,27 @@
1
1
  # ccstate-react
2
2
 
3
- ## 3.0.0
3
+ ## 4.1.0
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies [932cb80]
8
- - Updated dependencies [d3b721c]
9
- - Updated dependencies [7035e82]
10
- - ccstate@3.0.0
7
+ - Updated dependencies [2ce0d1d]
8
+ - ccstate@4.1.0
9
+
10
+ ## 4.0.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 42556f2: feat(react): add inline atom hooks
15
+ - 3e45895: feat: add default store
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [b32ad0a]
20
+ - Updated dependencies [3e45895]
21
+ - ccstate@4.0.0
22
+
23
+ ## 3.0.0
24
+
25
+ ### Major Changes
26
+
27
+ - 932cb80: refactor: expose react hooks into independent package 'ccstate-react'
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ var ccstate = require('ccstate');
4
+ var react = require('react');
5
+
6
+ var StoreContext = react.createContext(null);
7
+ StoreContext.Provider;
8
+ function useStore() {
9
+ var store = react.useContext(StoreContext);
10
+ if (!store) {
11
+ return ccstate.getDefaultStore();
12
+ }
13
+ return store;
14
+ }
15
+
16
+ function useRefFactory(factory) {
17
+ var ref = react.useRef(null);
18
+ if (!ref.current) {
19
+ var value = factory();
20
+ ref.current = value;
21
+ return value;
22
+ }
23
+ return ref.current;
24
+ }
25
+ function useCCState() {
26
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
27
+ args[_key] = arguments[_key];
28
+ }
29
+ return useRefFactory(function () {
30
+ return ccstate.state.apply(void 0, args);
31
+ });
32
+ }
33
+ function useComputed() {
34
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
35
+ args[_key2] = arguments[_key2];
36
+ }
37
+ return useRefFactory(function () {
38
+ return ccstate.computed.apply(void 0, args);
39
+ });
40
+ }
41
+ function useCommand() {
42
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
43
+ args[_key3] = arguments[_key3];
44
+ }
45
+ return useRefFactory(function () {
46
+ return ccstate.command.apply(void 0, args);
47
+ });
48
+ }
49
+ function useSub() {
50
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
51
+ args[_key4] = arguments[_key4];
52
+ }
53
+ var store = useStore();
54
+ react.useEffect(function () {
55
+ return store.sub.apply(store, args);
56
+ }, []);
57
+ }
58
+
59
+ exports.useCCState = useCCState;
60
+ exports.useCommand = useCommand;
61
+ exports.useComputed = useComputed;
62
+ exports.useSub = useSub;
@@ -0,0 +1,8 @@
1
+ import { state, State, computed, Computed, command, Command, Subscribe } from 'ccstate';
2
+
3
+ declare function useCCState<T>(...args: Parameters<typeof state<T>>): State<T>;
4
+ declare function useComputed<T>(...args: Parameters<typeof computed<T>>): Computed<T>;
5
+ declare function useCommand<T, Args extends unknown[]>(...args: Parameters<typeof command<T, Args>>): Command<T, Args>;
6
+ declare function useSub(...args: Parameters<Subscribe>): void;
7
+
8
+ export { useCCState, useCommand, useComputed, useSub };
@@ -0,0 +1,8 @@
1
+ import { state, State, computed, Computed, command, Command, Subscribe } from 'ccstate';
2
+
3
+ declare function useCCState<T>(...args: Parameters<typeof state<T>>): State<T>;
4
+ declare function useComputed<T>(...args: Parameters<typeof computed<T>>): Computed<T>;
5
+ declare function useCommand<T, Args extends unknown[]>(...args: Parameters<typeof command<T, Args>>): Command<T, Args>;
6
+ declare function useSub(...args: Parameters<Subscribe>): void;
7
+
8
+ export { useCCState, useCommand, useComputed, useSub };
@@ -0,0 +1,57 @@
1
+ import { getDefaultStore, state, computed, command } from 'ccstate';
2
+ import { createContext, useContext, useEffect, useRef } from 'react';
3
+
4
+ var StoreContext = createContext(null);
5
+ StoreContext.Provider;
6
+ function useStore() {
7
+ var store = useContext(StoreContext);
8
+ if (!store) {
9
+ return getDefaultStore();
10
+ }
11
+ return store;
12
+ }
13
+
14
+ function useRefFactory(factory) {
15
+ var ref = useRef(null);
16
+ if (!ref.current) {
17
+ var value = factory();
18
+ ref.current = value;
19
+ return value;
20
+ }
21
+ return ref.current;
22
+ }
23
+ function useCCState() {
24
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
25
+ args[_key] = arguments[_key];
26
+ }
27
+ return useRefFactory(function () {
28
+ return state.apply(void 0, args);
29
+ });
30
+ }
31
+ function useComputed() {
32
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
33
+ args[_key2] = arguments[_key2];
34
+ }
35
+ return useRefFactory(function () {
36
+ return computed.apply(void 0, args);
37
+ });
38
+ }
39
+ function useCommand() {
40
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
41
+ args[_key3] = arguments[_key3];
42
+ }
43
+ return useRefFactory(function () {
44
+ return command.apply(void 0, args);
45
+ });
46
+ }
47
+ function useSub() {
48
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
49
+ args[_key4] = arguments[_key4];
50
+ }
51
+ var store = useStore();
52
+ useEffect(function () {
53
+ return store.sub.apply(store, args);
54
+ }, []);
55
+ }
56
+
57
+ export { useCCState, useCommand, useComputed, useSub };
package/dist/index.cjs CHANGED
@@ -8,7 +8,7 @@ var StoreProvider = StoreContext.Provider;
8
8
  function useStore() {
9
9
  var store = react.useContext(StoreContext);
10
10
  if (!store) {
11
- throw new Error('Store context not found - did you forget to wrap your app with StoreProvider?');
11
+ return ccstate.getDefaultStore();
12
12
  }
13
13
  return store;
14
14
  }
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { createContext, useContext, useSyncExternalStore, useState, useEffect } from 'react';
2
- import { command } from 'ccstate';
2
+ import { getDefaultStore, command } from 'ccstate';
3
3
 
4
4
  var StoreContext = createContext(null);
5
5
  var StoreProvider = StoreContext.Provider;
6
6
  function useStore() {
7
7
  var store = useContext(StoreContext);
8
8
  if (!store) {
9
- throw new Error('Store context not found - did you forget to wrap your app with StoreProvider?');
9
+ return getDefaultStore();
10
10
  }
11
11
  return store;
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstate-react",
3
- "version": "3.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "CCState React Hooks",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,12 +14,18 @@
14
14
  ".": {
15
15
  "import": "./dist/index.js",
16
16
  "require": "./dist/index.cjs"
17
+ },
18
+ "./experimental": {
19
+ "import": "./dist/experimental.js",
20
+ "require": "./dist/experimental.cjs"
17
21
  }
18
22
  },
19
23
  "peerDependencies": {
20
24
  "@types/react": ">=17.0.0",
21
- "react": ">=17.0.0",
22
- "ccstate": "^3.0.0"
25
+ "react": ">=17.0.0"
26
+ },
27
+ "dependencies": {
28
+ "ccstate": "^4.1.0"
23
29
  },
24
30
  "peerDependenciesMeta": {
25
31
  "@types/react": {
@@ -35,21 +41,21 @@
35
41
  "@rollup/plugin-babel": "^6.0.4",
36
42
  "@rollup/plugin-node-resolve": "^15.3.0",
37
43
  "@testing-library/jest-dom": "^6.6.3",
38
- "@testing-library/react": "^16.0.1",
44
+ "@testing-library/react": "^16.1.0",
39
45
  "@testing-library/user-event": "^14.5.2",
40
- "@types/react": "^18.3.1",
46
+ "@types/react": "^19.0.2",
41
47
  "@types/react-dom": "^18.3.1",
42
48
  "happy-dom": "^15.11.7",
43
49
  "jest-leak-detector": "^29.7.0",
44
50
  "json": "^11.0.0",
45
- "react": "^18.3.1",
46
- "react-dom": "^18.3.1",
51
+ "react": "^19.0.0",
52
+ "react-dom": "^19.0.0",
47
53
  "rollup": "^4.28.1",
48
54
  "rollup-plugin-dts": "^6.1.1",
49
55
  "shx": "^0.3.4",
50
56
  "signal-timers": "^1.0.4",
51
57
  "vitest": "^2.1.8",
52
- "ccstate": "^3.0.0"
58
+ "ccstate": "^4.1.0"
53
59
  },
54
60
  "scripts": {
55
61
  "build": "rollup -c",
package/rollup.config.mjs CHANGED
@@ -77,8 +77,15 @@ function generateTarget({ input, targetCJS, targetES }) {
77
77
  }
78
78
 
79
79
  /** @type { Array<import('rollup').RollupOptions> } */
80
- export default generateTarget({
81
- input: './src/index.ts',
82
- targetCJS: './dist/index.cjs',
83
- targetES: './dist/index.js',
84
- });
80
+ export default [
81
+ ...generateTarget({
82
+ input: './src/index.ts',
83
+ targetCJS: './dist/index.cjs',
84
+ targetES: './dist/index.js',
85
+ }),
86
+ ...generateTarget({
87
+ input: './src/experimental.ts',
88
+ targetCJS: './dist/experimental.cjs',
89
+ targetES: './dist/experimental.js',
90
+ }),
91
+ ];
@@ -1,8 +1,7 @@
1
- // @vitest-environment happy-dom
2
1
  import { render, cleanup, screen } from '@testing-library/react';
3
2
  import userEvent from '@testing-library/user-event';
4
3
  import { afterEach, describe, expect, it, vi } from 'vitest';
5
- import { computed, createStore, command, state, createDebugStore } from 'ccstate';
4
+ import { computed, createStore, command, state, createDebugStore, getDefaultStore } from 'ccstate';
6
5
  import { StoreProvider, useGet, useSet } from '..';
7
6
  import { StrictMode, useState } from 'react';
8
7
  import '@testing-library/jest-dom/vitest';
@@ -199,25 +198,21 @@ describe('react', () => {
199
198
  expect(await screen.findByText('1')).toBeInTheDocument();
200
199
  });
201
200
 
202
- it('will throw error if no provider', () => {
201
+ it('should use default store if no provider', () => {
203
202
  const count$ = state(0);
203
+ getDefaultStore().set(count$, 10);
204
204
 
205
205
  function App() {
206
206
  const count = useGet(count$);
207
207
  return <div>{count}</div>;
208
208
  }
209
209
 
210
- // suppress react render error message in console
211
- const mock = vi.spyOn(console, 'error').mockImplementation(() => void 0);
212
- expect(() =>
213
- render(
214
- <StrictMode>
215
- <App />
216
- </StrictMode>,
217
- ),
218
- ).toThrow();
219
-
220
- mock.mockRestore();
210
+ render(
211
+ <StrictMode>
212
+ <App />
213
+ </StrictMode>,
214
+ );
215
+ expect(screen.getByText('10')).toBeInTheDocument();
221
216
  });
222
217
 
223
218
  it('will unmount when component cleanup', async () => {
@@ -0,0 +1,139 @@
1
+ import { afterEach, expect, it } from 'vitest';
2
+ import { StoreProvider, useGet, useSet } from '..';
3
+ import '@testing-library/jest-dom/vitest';
4
+ import { screen, cleanup, render } from '@testing-library/react';
5
+ import { StrictMode } from 'react';
6
+ import userEvent from '@testing-library/user-event';
7
+ import { useCCState, useCommand, useComputed, useSub } from '../experimental';
8
+ import { createDebugStore } from 'ccstate';
9
+
10
+ afterEach(() => {
11
+ cleanup();
12
+ });
13
+ const user = userEvent.setup();
14
+
15
+ it('use atom in React component', async () => {
16
+ function Counter() {
17
+ const count$ = useCCState(0, {
18
+ debugLabel: 'count$',
19
+ });
20
+ const double$ = useComputed(
21
+ (get) => {
22
+ return get(count$) * 2;
23
+ },
24
+ {
25
+ debugLabel: 'double$',
26
+ },
27
+ );
28
+ const incrementTriple$ = useCommand(
29
+ ({ get, set }, diff: number) => {
30
+ set(count$, get(count$) + diff * 3);
31
+ },
32
+ {
33
+ debugLabel: 'incrementTriple$',
34
+ },
35
+ );
36
+
37
+ const count = useGet(count$);
38
+ const double = useGet(double$);
39
+
40
+ const setCount = useSet(count$);
41
+ const incrementTriple = useSet(incrementTriple$);
42
+
43
+ return (
44
+ <>
45
+ <div>count: {String(count)}</div>
46
+ <div>double: {String(double)}</div>
47
+ <button
48
+ onClick={() => {
49
+ setCount((prev) => prev + 1);
50
+ }}
51
+ >
52
+ Increment
53
+ </button>
54
+ <button
55
+ onClick={() => {
56
+ incrementTriple(1);
57
+ }}
58
+ >
59
+ Increment Triple
60
+ </button>
61
+ </>
62
+ );
63
+ }
64
+
65
+ render(
66
+ <StrictMode>
67
+ <Counter />
68
+ </StrictMode>,
69
+ );
70
+ expect(screen.getByText('count: 0')).toBeInTheDocument();
71
+
72
+ const button = screen.getByText('Increment');
73
+ await user.click(button);
74
+ expect(screen.getByText('count: 1')).toBeInTheDocument();
75
+ expect(screen.getByText('double: 2')).toBeInTheDocument();
76
+
77
+ const incrementTripleButton = screen.getByText('Increment Triple');
78
+ await user.click(incrementTripleButton);
79
+ expect(screen.getByText('count: 4')).toBeInTheDocument();
80
+ expect(screen.getByText('double: 8')).toBeInTheDocument();
81
+ });
82
+
83
+ it('use sub in React component', async () => {
84
+ function Counter() {
85
+ const count$ = useCCState(0, {
86
+ debugLabel: 'count$',
87
+ });
88
+ const double$ = useCCState(0, {
89
+ debugLabel: 'double$',
90
+ });
91
+
92
+ const updateDouble$ = useCommand(
93
+ ({ get, set }) => {
94
+ const double = get(count$) * 2;
95
+ set(double$, double);
96
+ },
97
+ {
98
+ debugLabel: 'updateDouble$',
99
+ },
100
+ );
101
+ useSub(count$, updateDouble$);
102
+
103
+ const count = useGet(count$);
104
+ const double = useGet(double$);
105
+ const setCount = useSet(count$);
106
+
107
+ return (
108
+ <>
109
+ <div>count: {String(count)}</div>
110
+ <div>double: {String(double)}</div>
111
+ <button
112
+ onClick={() => {
113
+ setCount((prev) => prev + 1);
114
+ }}
115
+ >
116
+ Increment
117
+ </button>
118
+ </>
119
+ );
120
+ }
121
+
122
+ const store = createDebugStore();
123
+ render(
124
+ <StrictMode>
125
+ <StoreProvider value={store}>
126
+ <Counter />
127
+ </StoreProvider>
128
+ </StrictMode>,
129
+ );
130
+ expect(screen.getByText('count: 0')).toBeInTheDocument();
131
+
132
+ const button = screen.getByText('Increment');
133
+ await user.click(button);
134
+ expect(screen.getByText('count: 1')).toBeInTheDocument();
135
+ expect(await screen.findByText('double: 2')).toBeInTheDocument();
136
+
137
+ cleanup();
138
+ expect(store.getSubscribeGraph()).toEqual([]);
139
+ });
@@ -0,0 +1 @@
1
+ export { useCCState, useComputed, useCommand, useSub } from './useInlineAtom';
package/src/provider.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createContext, useContext } from 'react';
2
+ import { getDefaultStore } from 'ccstate';
2
3
  import type { Store } from 'ccstate';
3
4
 
4
5
  const StoreContext = createContext<Store | null>(null);
@@ -9,7 +10,7 @@ export function useStore(): Store {
9
10
  const store = useContext(StoreContext);
10
11
 
11
12
  if (!store) {
12
- throw new Error('Store context not found - did you forget to wrap your app with StoreProvider?');
13
+ return getDefaultStore();
13
14
  }
14
15
 
15
16
  return store;
@@ -0,0 +1,40 @@
1
+ import { command, computed, state, type Command, type Computed, type State, type Subscribe } from 'ccstate';
2
+ import { useEffect, useRef } from 'react';
3
+ import { useStore } from './provider';
4
+
5
+ function useRefFactory<T>(factory: () => T): T {
6
+ const ref = useRef<T | null>(null);
7
+ if (!ref.current) {
8
+ const value = factory();
9
+ ref.current = value;
10
+ return value;
11
+ }
12
+
13
+ return ref.current;
14
+ }
15
+
16
+ export function useCCState<T>(...args: Parameters<typeof state<T>>): State<T> {
17
+ return useRefFactory<State<T>>(() => {
18
+ return state(...args);
19
+ });
20
+ }
21
+
22
+ export function useComputed<T>(...args: Parameters<typeof computed<T>>): Computed<T> {
23
+ return useRefFactory<Computed<T>>(() => {
24
+ return computed(...args);
25
+ });
26
+ }
27
+
28
+ export function useCommand<T, Args extends unknown[]>(...args: Parameters<typeof command<T, Args>>): Command<T, Args> {
29
+ return useRefFactory<Command<T, Args>>(() => {
30
+ return command(...args);
31
+ });
32
+ }
33
+
34
+ export function useSub(...args: Parameters<Subscribe>) {
35
+ const store = useStore();
36
+
37
+ useEffect(() => {
38
+ return store.sub(...args);
39
+ }, []);
40
+ }
package/vitest.config.ts CHANGED
@@ -1,3 +1,7 @@
1
1
  import { defineConfig } from 'vitest/config';
2
2
 
3
- export default defineConfig({});
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'happy-dom',
6
+ },
7
+ });