reactish-state 1.0.0-alpha.1 → 1.0.0-alpha.3

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
@@ -2,7 +2,9 @@
2
2
 
3
3
  > Simple, decentralized (atomic) state management for React.
4
4
 
5
- [![NPM](https://img.shields.io/npm/v/reactish-state.svg)](https://www.npmjs.com/package/reactish-state) [![NPM](https://img.shields.io/bundlephobia/minzip/reactish-state)](https://bundlephobia.com/package/reactish-state)
5
+ [![NPM](https://img.shields.io/npm/v/reactish-state.svg)](https://www.npmjs.com/package/reactish-state) [![NPM](https://img.shields.io/bundlephobia/minzip/reactish-state)](https://bundlephobia.com/package/reactish-state) [![bundlejs](https://img.shields.io/badge/bundlejs-.com-blue.svg)](https://bundlejs.com/?q=reactish-state&treeshake=%5B*%5D&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D)
6
+
7
+ 💡 [Quick examples](#examples)    🔧 [TypeScript usage](#typescript-usage)
6
8
 
7
9
  ## ✨Highlights✨
8
10
 
@@ -43,7 +45,7 @@ countState.set(10);
43
45
  console.log(countState.get()); // Print 10
44
46
  ```
45
47
 
46
- ### A state can also have actions bound to it
48
+ ### A state can also have custom actions bound to it
47
49
 
48
50
  ```js
49
51
  const countState = state(0, (set, get) => ({
@@ -55,8 +57,8 @@ const countState = state(0, (set, get) => ({
55
57
  decrease: () => set(get() - 1)
56
58
  }));
57
59
 
58
- // Use the actions
59
- countState.actions.increase();
60
+ // Use the custom actions
61
+ countState.increase();
60
62
  ```
61
63
 
62
64
  ### `selector` can create derived state
@@ -90,9 +92,10 @@ const Example = () => {
90
92
 
91
93
  return (
92
94
  <h1>
95
+ {/* The return values of `useSnapshot` are used for rendering */}
93
96
  {count} {triple}
94
- {/* Update the state using the actions bound to it */}
95
- <button onClick={() => countState.actions.increase()}>Increase</button>
97
+ {/* Update the state using the custom actions bound to it */}
98
+ <button onClick={() => countState.increase()}>Increase</button>
96
99
  {/* Or update the state using the `set` method directly */}
97
100
  <button onClick={() => countState.set((i) => i - 1)}>Decrease</button>
98
101
  <button onClick={() => countState.set(0)}>Reset</button>
@@ -232,11 +235,14 @@ You can also create async actions bound to a state:
232
235
 
233
236
  ```js
234
237
  const todosState = state([], (set) => ({
235
- fetch: async (url) => {
236
- const response = await fetch(url);
238
+ fetchData: async () => {
239
+ const response = await fetch(/* some url */);
237
240
  set(await response.json());
238
241
  }
239
242
  }));
243
+
244
+ // Use the async action
245
+ await todosState.fetchData();
240
246
  ```
241
247
 
242
248
  ## Accessing other state or selectors inside actions
@@ -293,7 +299,7 @@ const countState = state(0, (set) => ({
293
299
  increase: () => set((count) => count + 1),
294
300
  reset: () => set(0)
295
301
  }));
296
- const { increase, reset } = countState.actions;
302
+ const { increase, reset } = countState;
297
303
 
298
304
  const Example = () => {
299
305
  const count = useSnapshot(countState);
@@ -372,7 +378,7 @@ const countState = state(0, (set) => ({
372
378
  dispatch: (action) => set((state) => reducer(state, action), action)
373
379
  }));
374
380
 
375
- const { dispatch } = countState.actions;
381
+ const { dispatch } = countState;
376
382
  dispatch({ type: "INCREASE", by: 10 });
377
383
  dispatch({ type: "DECREASE", by: 7 });
378
384
  console.log(countState.get()); // Print 3
@@ -401,7 +407,7 @@ const countState = state(0, (set) => ({
401
407
  }));
402
408
 
403
409
  countState.set(99); // Print "New state 99"
404
- countState.actions.increase(); // Print "New state 100"
410
+ countState.increase(); // Print "New state 100"
405
411
 
406
412
  // The same `state` function can be reused,
407
413
  // so you don't need to set up the middleware again
@@ -473,8 +479,8 @@ const todos = state([], (set) => ({
473
479
  }));
474
480
 
475
481
  // Use the actions
476
- todos.actions.add("Shop groceries");
477
- todos.actions.toggle(1);
482
+ todos.add("Shop groceries");
483
+ todos.toggle(1);
478
484
  ```
479
485
 
480
486
  ## Redux devtools middleware
@@ -588,6 +594,68 @@ const selector = createSelector({ plugin: reduxDevtools() });
588
594
  // Then use the `selector` as usual...
589
595
  ```
590
596
 
597
+ # TypeScript usage
598
+
599
+ The API relies on type inference to correctly infer the types for both the value and actions of the state. There are two scenarios:
600
+
601
+ ## I. The type of state can be inferred from its initial value
602
+
603
+ In this case, the usage in TypeScript should be identical to JavaScript. You don't need to make any specific effort regarding typing. This is true when the state holds simple or primitive values.
604
+
605
+ ```ts
606
+ const countState = state(0, (set) => ({
607
+ increase: (by: number) =>
608
+ set(
609
+ (count) => count + by
610
+ // The `count` is inferred as a number type from the initial value.
611
+ )
612
+ }));
613
+ ```
614
+
615
+ ## II. The type of state cannot be inferred from its initial value
616
+
617
+ In this case, you have three options:
618
+
619
+ ### 1. Use a type assertion to specify a more specific type for the initial value:
620
+
621
+ ```ts
622
+ const myTodos = state([] as string[], (set) => ({
623
+ add: (newTodo: string) => set((todos) => [...todos, newTodo])
624
+ }));
625
+ ```
626
+
627
+ This is the simplest approach since the types for custom actions will be automatically inferred.
628
+
629
+ ### 2. Declare the initial value separately with a specific type:
630
+
631
+ ```ts
632
+ const initialValue: string[] = [];
633
+ const myTodos = state(initialValue, (set) => ({
634
+ add: (newTodo: string) => set((todos) => [...todos, newTodo])
635
+ }));
636
+ ```
637
+
638
+ This is basically very similar to the first method, except you need to write an additional line of code. The types for actions will be automatically inferred.
639
+
640
+ ### 3. Specify type parameters explicitly:
641
+
642
+ ```ts
643
+ const myTodos = state<string[], { add: (newTodo: string) => void }>(
644
+ [],
645
+ (set) => ({
646
+ add: (newTodo) => set((todos) => [...todos, newTodo])
647
+ })
648
+ );
649
+ ```
650
+
651
+ However, if you choose this method, you need to specify the types for both the state value and actions.
652
+
653
+ # Examples
654
+
655
+ - Counter – [sandbox](https://codesandbox.io/p/sandbox/reactish-counter-z42qt7) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/counter)
656
+ - Todo app – [sandbox](https://codesandbox.io/s/reactish-todo-thyhbl) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/todo)
657
+ - Async – [sandbox](https://codesandbox.io/s/reactish-async-2cghkg) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/async)
658
+
591
659
  # React 16/17 setup
592
660
 
593
661
  When using this library with React 16/17, you must set up a shim since it doesn't include a native [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore). We don't set up the shim by default to minimize the bundle size for React 18/19 users.
@@ -599,9 +667,3 @@ setReactShim(reactShim);
599
667
  ```
600
668
 
601
669
  You only need to set it up once after your app launches, outside of React code. DO NOT call `setReactShim` within any React components.
602
-
603
- # Examples
604
-
605
- - Counter – [sandbox](https://codesandbox.io/p/sandbox/reactish-counter-z42qt7) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/counter)
606
- - Todo app – [sandbox](https://codesandbox.io/s/reactish-todo-thyhbl) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/todo)
607
- - Async – [sandbox](https://codesandbox.io/s/reactish-async-2cghkg) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/async)
@@ -7,7 +7,7 @@ const useSnapshot = ({
7
7
  get
8
8
  }) => {
9
9
  if (process.env.NODE_ENV !== 'production' && !shim.useSyncExternalStore) {
10
- throw new Error('[reactish-state] Shim setup is required for React 16/17.');
10
+ throw new Error('[reactish-state] Shim setup is required for React 16/17. See: https://github.com/szhsin/reactish-state/tree/master?tab=readme-ov-file#react-1617-setup');
11
11
  }
12
12
  return shim.useSyncExternalStore(subscribe, get, get);
13
13
  };
@@ -23,10 +23,10 @@ const createState = ({
23
23
  subscribe
24
24
  }, config);
25
25
  return {
26
+ ...actionCreator?.(set, get),
26
27
  get,
27
28
  set,
28
- subscribe,
29
- actions: actionCreator?.(set, get)
29
+ subscribe
30
30
  };
31
31
  };
32
32
  const state = /*#__PURE__*/createState();
@@ -5,7 +5,7 @@ const useSnapshot = ({
5
5
  get
6
6
  }) => {
7
7
  if (process.env.NODE_ENV !== 'production' && !useSyncExternalStore) {
8
- throw new Error('[reactish-state] Shim setup is required for React 16/17.');
8
+ throw new Error('[reactish-state] Shim setup is required for React 16/17. See: https://github.com/szhsin/reactish-state/tree/master?tab=readme-ov-file#react-1617-setup');
9
9
  }
10
10
  return useSyncExternalStore(subscribe, get, get);
11
11
  };
@@ -21,10 +21,10 @@ const createState = ({
21
21
  subscribe
22
22
  }, config);
23
23
  return {
24
+ ...actionCreator?.(set, get),
24
25
  get,
25
26
  set,
26
- subscribe,
27
- actions: actionCreator?.(set, get)
27
+ subscribe
28
28
  };
29
29
  };
30
30
  const state = /*#__PURE__*/createState();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reactish-state",
3
- "version": "1.0.0-alpha.1",
4
- "description": "Simple, decentralized state management for React.",
3
+ "version": "1.0.0-alpha.3",
4
+ "description": "Simple, decentralized (atomic) state management for React.",
5
5
  "author": "Zheng Song",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "lint:fix": "eslint --fix .",
38
38
  "pret": "prettier -c .",
39
39
  "pret:fix": "prettier -w .",
40
- "build": "run-s pret lint clean types bundle",
40
+ "build": "run-s pret clean types lint bundle",
41
41
  "test": "jest",
42
42
  "test:watch": "jest --watch",
43
43
  "eg": "npm run dev --prefix examples"
@@ -1,12 +1,12 @@
1
1
  import type { Reactish, Setter, Config, Middleware } from '../common';
2
2
  type ActionCreator<T, A> = ((set: Setter<T>, get: () => T) => A) | null | undefined;
3
- interface State<T, A = unknown, C extends ActionCreator<T, A> = undefined> extends Reactish<T> {
3
+ type VanillaState<T> = Reactish<T> & {
4
4
  set: Setter<T>;
5
- actions: C extends undefined ? never : A;
6
- }
5
+ };
6
+ type State<T, A> = Omit<A, keyof VanillaState<T>> & VanillaState<T>;
7
7
  declare const createState: ({ middleware }?: {
8
8
  middleware?: Middleware;
9
- }) => <T, A>(initialValue: T, actionCreator?: ActionCreator<T, A>, config?: Config) => State<T, A, ActionCreator<T, A>>;
10
- declare const state: <T, A>(initialValue: T, actionCreator?: ActionCreator<T, A>, config?: Config) => State<T, A, ActionCreator<T, A>>;
9
+ }) => <T, A>(initialValue: T, actionCreator?: ActionCreator<T, A>, config?: Config) => State<T, A>;
10
+ declare const state: <T, A>(initialValue: T, actionCreator?: ActionCreator<T, A>, config?: Config) => State<T, A>;
11
11
  export type { State, ActionCreator };
12
12
  export { state, createState };