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

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
@@ -43,7 +43,7 @@ countState.set(10);
43
43
  console.log(countState.get()); // Print 10
44
44
  ```
45
45
 
46
- ### A state can also have actions bound to it
46
+ ### A state can also have custom actions bound to it
47
47
 
48
48
  ```js
49
49
  const countState = state(0, (set, get) => ({
@@ -55,8 +55,8 @@ const countState = state(0, (set, get) => ({
55
55
  decrease: () => set(get() - 1)
56
56
  }));
57
57
 
58
- // Use the actions
59
- countState.actions.increase();
58
+ // Use the custom actions
59
+ countState.increase();
60
60
  ```
61
61
 
62
62
  ### `selector` can create derived state
@@ -90,9 +90,10 @@ const Example = () => {
90
90
 
91
91
  return (
92
92
  <h1>
93
+ {/* The return values of `useSnapshot` are used for rendering */}
93
94
  {count} {triple}
94
- {/* Update the state using the actions bound to it */}
95
- <button onClick={() => countState.actions.increase()}>Increase</button>
95
+ {/* Update the state using the custom actions bound to it */}
96
+ <button onClick={() => countState.increase()}>Increase</button>
96
97
  {/* Or update the state using the `set` method directly */}
97
98
  <button onClick={() => countState.set((i) => i - 1)}>Decrease</button>
98
99
  <button onClick={() => countState.set(0)}>Reset</button>
@@ -232,11 +233,14 @@ You can also create async actions bound to a state:
232
233
 
233
234
  ```js
234
235
  const todosState = state([], (set) => ({
235
- fetch: async (url) => {
236
- const response = await fetch(url);
236
+ fetchData: async () => {
237
+ const response = await fetch(/* some url */);
237
238
  set(await response.json());
238
239
  }
239
240
  }));
241
+
242
+ // Use the async action
243
+ await todosState.fetchData();
240
244
  ```
241
245
 
242
246
  ## Accessing other state or selectors inside actions
@@ -293,7 +297,7 @@ const countState = state(0, (set) => ({
293
297
  increase: () => set((count) => count + 1),
294
298
  reset: () => set(0)
295
299
  }));
296
- const { increase, reset } = countState.actions;
300
+ const { increase, reset } = countState;
297
301
 
298
302
  const Example = () => {
299
303
  const count = useSnapshot(countState);
@@ -372,7 +376,7 @@ const countState = state(0, (set) => ({
372
376
  dispatch: (action) => set((state) => reducer(state, action), action)
373
377
  }));
374
378
 
375
- const { dispatch } = countState.actions;
379
+ const { dispatch } = countState;
376
380
  dispatch({ type: "INCREASE", by: 10 });
377
381
  dispatch({ type: "DECREASE", by: 7 });
378
382
  console.log(countState.get()); // Print 3
@@ -401,7 +405,7 @@ const countState = state(0, (set) => ({
401
405
  }));
402
406
 
403
407
  countState.set(99); // Print "New state 99"
404
- countState.actions.increase(); // Print "New state 100"
408
+ countState.increase(); // Print "New state 100"
405
409
 
406
410
  // The same `state` function can be reused,
407
411
  // so you don't need to set up the middleware again
@@ -473,8 +477,8 @@ const todos = state([], (set) => ({
473
477
  }));
474
478
 
475
479
  // Use the actions
476
- todos.actions.add("Shop groceries");
477
- todos.actions.toggle(1);
480
+ todos.add("Shop groceries");
481
+ todos.toggle(1);
478
482
  ```
479
483
 
480
484
  ## Redux devtools middleware
@@ -588,6 +592,52 @@ const selector = createSelector({ plugin: reduxDevtools() });
588
592
  // Then use the `selector` as usual...
589
593
  ```
590
594
 
595
+ # TypeScript usage
596
+
597
+ The API relies on type inference to correctly infer the types for both the value and actions of the state. There are two scenarios:
598
+
599
+ ## I. The type of state can be inferred from its initial value
600
+
601
+ 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.
602
+
603
+ ## II. The type of state cannot be inferred from its initial value
604
+
605
+ In this case, you have three options:
606
+
607
+ ### 1. Use a type assertion to specify a more specific type for the initial value:
608
+
609
+ ```ts
610
+ const myTodos = state([] as string[], (set) => ({
611
+ add: (newTodo: string) => set((todos) => [...todos, newTodo])
612
+ }));
613
+ ```
614
+
615
+ This is the simplest approach since the types for custom actions will be automatically inferred.
616
+
617
+ ### 2. Declare the initial value separately with a specific type:
618
+
619
+ ```ts
620
+ const initialValue: string[] = [];
621
+ const myTodos = state(initialValue, (set) => ({
622
+ add: (newTodo: string) => set((todos) => [...todos, newTodo])
623
+ }));
624
+ ```
625
+
626
+ 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.
627
+
628
+ ### 3. Specify type parameters explicitly:
629
+
630
+ ```ts
631
+ const myTodos = state<string[], { add: (newTodo: string) => void }>(
632
+ [],
633
+ (set) => ({
634
+ add: (newTodo) => set((todos) => [...todos, newTodo])
635
+ })
636
+ );
637
+ ```
638
+
639
+ However, if you choose this method, you need to specify the types for both the state value and actions.
640
+
591
641
  # React 16/17 setup
592
642
 
593
643
  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.
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "reactish-state",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.2",
4
4
  "description": "Simple, decentralized state management for React.",
5
5
  "author": "Zheng Song",
6
6
  "license": "MIT",
@@ -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 };