reactish-state 0.11.2 → 1.0.0-alpha.1

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 (51) hide show
  1. package/README.md +100 -84
  2. package/dist/cjs/index.cjs +17 -0
  3. package/dist/cjs/react/shim.cjs +10 -0
  4. package/dist/cjs/react/useSelector.cjs +35 -0
  5. package/dist/cjs/react/useSnapshot.cjs +15 -0
  6. package/dist/cjs/utils.cjs +17 -0
  7. package/dist/cjs/vanilla/selector.cjs +35 -0
  8. package/dist/cjs/vanilla/state.cjs +35 -0
  9. package/dist/esm/index.mjs +5 -0
  10. package/dist/esm/react/shim.mjs +8 -0
  11. package/dist/esm/react/{useSelector.js → useSelector.mjs} +2 -2
  12. package/dist/esm/react/useSnapshot.mjs +13 -0
  13. package/dist/esm/vanilla/{selector.js → selector.mjs} +2 -2
  14. package/dist/esm/vanilla/{state.js → state.mjs} +1 -1
  15. package/dist/middleware/cjs/applyMiddleware.cjs +10 -0
  16. package/dist/middleware/cjs/index.cjs +11 -0
  17. package/dist/middleware/cjs/persist.cjs +39 -0
  18. package/dist/middleware/cjs/reduxDevtools.cjs +34 -0
  19. package/dist/middleware/esm/index.mjs +3 -0
  20. package/dist/middleware/esm/{persist.js → persist.mjs} +1 -1
  21. package/dist/middleware/esm/{reduxDevtools.js → reduxDevtools.mjs} +1 -1
  22. package/dist/plugin/cjs/applyPlugin.cjs +5 -0
  23. package/dist/plugin/cjs/index.cjs +9 -0
  24. package/dist/plugin/cjs/{index.js → reduxDevtools.cjs} +1 -4
  25. package/dist/plugin/esm/{applyPlugin.js → applyPlugin.mjs} +1 -1
  26. package/dist/plugin/esm/index.mjs +2 -0
  27. package/dist/plugin/esm/{reduxDevtools.js → reduxDevtools.mjs} +1 -1
  28. package/dist/shim/cjs/index.cjs +7 -0
  29. package/dist/shim/cjs/reactShim.cjs +7 -0
  30. package/dist/shim/esm/index.mjs +1 -0
  31. package/dist/shim/esm/reactShim.mjs +5 -0
  32. package/package.json +59 -43
  33. package/shim/index.d.ts +1 -0
  34. package/types/index.d.ts +1 -0
  35. package/types/middleware/persist.d.ts +1 -1
  36. package/types/react/shim.d.ts +4 -0
  37. package/types/react/useSelector.d.ts +2 -2
  38. package/types/shim/index.d.ts +1 -0
  39. package/types/shim/reactShim.d.ts +3 -0
  40. package/types/vanilla/selector.d.ts +1 -1
  41. package/types/vanilla/state.d.ts +1 -1
  42. package/dist/cjs/index.js +0 -116
  43. package/dist/esm/index.js +0 -4
  44. package/dist/esm/react/useSnapshot.js +0 -8
  45. package/dist/middleware/cjs/index.js +0 -79
  46. package/dist/middleware/esm/index.js +0 -3
  47. package/dist/plugin/esm/index.js +0 -2
  48. /package/dist/esm/{utils.js → utils.mjs} +0 -0
  49. /package/dist/middleware/cjs/{immer.js → immer.cjs} +0 -0
  50. /package/dist/middleware/esm/{applyMiddleware.js → applyMiddleware.mjs} +0 -0
  51. /package/dist/middleware/esm/{immer.js → immer.mjs} +0 -0
package/README.md CHANGED
@@ -1,41 +1,45 @@
1
1
  # Reactish-State
2
2
 
3
- > Simple, decentralized(atomic) state management for React.
3
+ > Simple, decentralized (atomic) state management for React.
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)
4
6
 
5
7
  ## ✨Highlights✨
6
8
 
7
9
  - Decentralized state management
8
- - Un-opinionated and easy-to-use API
9
- - No need of wrapping app in Context or prop drilling
10
+ - Unopinionated and easy-to-use API
11
+ - No need to wrap app in Context or prop drilling
10
12
  - React components re-render only on changes
11
- - Compatible with React 18 concurrent rendering
13
+ - Compatible with React 18/19 concurrent rendering
12
14
  - Selectors are memoized by default
13
15
  - Feature extensible with middleware or plugins
14
- - States persistable to browser storage
15
- - Support Redux dev tools via middleware
16
- - [~1KB](https://bundlephobia.com/package/reactish-state): simple and small
16
+ - State persistable to browser storage
17
+ - Support for Redux dev tools via middleware
18
+ - [Less than 1KB](https://bundlejs.com/?q=reactish-state&treeshake=%5B*%5D&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D): simple and small
17
19
 
18
20
  ## Install
19
21
 
20
- `npm install reactish-state` or `yarn add reactish-state`
22
+ ```bash
23
+ npm install reactish-state
24
+ ```
21
25
 
22
26
  ## Quick start
23
27
 
24
- ### We begin by creating some states
28
+ ### We begin by creating some state
25
29
 
26
30
  ```js
27
31
  import { state } from "reactish-state";
28
32
 
29
- // `state` can hold anything: primitives, arrays, objects...
33
+ // `state` can hold anything: primitives, arrays, objects, etc.
30
34
  const countState = state(0);
31
35
  const todos = state([
32
36
  { task: "Shop groceries", completed: false },
33
37
  { task: "Clean the house", completed: true }
34
38
  ]);
35
39
 
36
- // Update state
40
+ // Update the state
37
41
  countState.set(10);
38
- // Read from state
42
+ // Read from the state
39
43
  console.log(countState.get()); // Print 10
40
44
  ```
41
45
 
@@ -43,19 +47,19 @@ console.log(countState.get()); // Print 10
43
47
 
44
48
  ```js
45
49
  const countState = state(0, (set, get) => ({
46
- // Set a new state
50
+ // Set a new state value
47
51
  reset: () => set(0),
48
- // or using the functional update of `set`
52
+ // Or use the functional update of `set`
49
53
  increase: () => set((count) => count + 1),
50
54
  // State can still be read using `get`
51
55
  decrease: () => set(get() - 1)
52
56
  }));
53
57
 
54
- // Using the actions
58
+ // Use the actions
55
59
  countState.actions.increase();
56
60
  ```
57
61
 
58
- ### `selector` can create derived states
62
+ ### `selector` can create derived state
59
63
 
60
64
  ```js
61
65
  import { selector } from "reactish-state";
@@ -71,11 +75,11 @@ const tripleSelector = selector(
71
75
  );
72
76
  ```
73
77
 
74
- A selector will re-compute only when any of the states it depends on have changed.
78
+ A selector will re-compute only when one of the states it depends on has changed.
75
79
 
76
- ### Use the state and selector in your React components
80
+ ### Use the state and selectors in your React components
77
81
 
78
- You can read state and selector for rendering with the `useSnapshot` hook, and write to state with `set` or actions. _Rule of thumb_: always read from `useSnapshot` in render function, otherwise use the `get` method of state or selector.
82
+ You can read state and selectors for rendering with the `useSnapshot` hook, and write to state with `set` or actions. _Rule of thumb_: always read from `useSnapshot` in the render function; otherwise, use the `get` method of state or selector (in event handlers or even outside of React components).
79
83
 
80
84
  ```jsx
81
85
  import { useSnapshot } from "reactish-state";
@@ -87,9 +91,9 @@ const Example = () => {
87
91
  return (
88
92
  <h1>
89
93
  {count} {triple}
90
- {/* Update state using the actions bound to it */}
94
+ {/* Update the state using the actions bound to it */}
91
95
  <button onClick={() => countState.actions.increase()}>Increase</button>
92
- {/* Or update state using the `set` method directly */}
96
+ {/* Or update the state using the `set` method directly */}
93
97
  <button onClick={() => countState.set((i) => i - 1)}>Decrease</button>
94
98
  <button onClick={() => countState.set(0)}>Reset</button>
95
99
  </h1>
@@ -97,19 +101,19 @@ const Example = () => {
97
101
  };
98
102
  ```
99
103
 
100
- The component will re-render when states or selectors have changed. No provider or context are needed!
104
+ The component will re-render when states or selectors change. No provider or context is needed!
101
105
 
102
106
  **[Try a sandbox demo!](https://codesandbox.io/p/sandbox/reactish-counter-z42qt7)**
103
107
 
104
108
  ## Why another state management library?
105
109
 
106
- The state management solutions in the React ecosystem have popularized two state models:
110
+ State management solutions in the React ecosystem have popularized two state models:
107
111
 
108
- - **Centralized**: a single store that combines entire app states together and slices of the store are connected to React components through selectors. Examples: react-redux, Zustand.
112
+ - **Centralized**: a single store that combines the entire app's state, with slices of the store connected to React components via selectors. Examples: React-Redux, Zustand.
109
113
 
110
- - **Decentralized**: consisting of many small(atomic) states which can build up state dependency trees using a bottom-up approach. React components only need to connect with the states that they use. Examples: Recoil, Jotai.
114
+ - **Decentralized**: composed of many small (atomic) states that build state dependency trees using a bottom-up approach. React components only connect to the states they need. Examples: Recoil, Jotai.
111
115
 
112
- This library adopts the decentralized state model, offering a _Recoil-like_ API, but with a much simpler and smaller implementation(similar to Zustand), which makes it the one of the smallest state management solutions with gzipped bundle size around 1KB.
116
+ This library adopts the decentralized state model, offering a _Recoil-like_ API with a much smaller implementation (similar to Zustand). This makes it one of the smallest state management solutions, with a gzipped bundle size of less than 1KB.
113
117
 
114
118
  | | State model | Bundle size |
115
119
  | --- | --- | --- |
@@ -121,18 +125,18 @@ This library adopts the decentralized state model, offering a _Recoil-like_ API,
121
125
 
122
126
  ## Why decentralized state management?
123
127
 
124
- Centralized state management usually combines the entire app states into a single store. To achieve render optimization, selectors are used to subscribe React components to slices of the store. Taking the classic [Redux todo example](https://redux.js.org/introduction/examples#todos), the store has the following shape:
128
+ Centralized state management typically combines the entire app's state into a single store. To optimize rendering, selectors are used to subscribe React components to slices of the store. Taking the classic [Redux todo example](https://redux.js.org/introduction/examples#todos), the store has the following shape:
125
129
 
126
130
  ```js
127
131
  {
128
132
  visibilityFilter: "ALL", // ALL, ACTIVE, COMPLETED
129
- todos: [{ task: "Shop groceries", completed: false } /* ...more items */]
133
+ todos: [{ task: "Shop groceries", completed: false } /* ...and more items */]
130
134
  }
131
135
  ```
132
136
 
133
137
  We have a `<Filter/>` component that connects to the store with a selector `(state) => state.visibilityFilter`.
134
138
 
135
- When any action updates the `todos` slice, the selector in the `<Filter/>` component needs to re-run to determine if a re-rendering of the component is required. This is not optimal as `<Filter/>` component should not even be bothered when the todos are added/removed/updated.
139
+ When any action updates the `todos` slice, the selector in the `<Filter/>` component needs to re-run to determine if a re-render is required. This is not optimal, as the `<Filter/>` component should not be affected when todos are added, removed, or updated.
136
140
 
137
141
  In contrast, decentralized state management may approach the same problem with two separate states:
138
142
 
@@ -140,24 +144,24 @@ In contrast, decentralized state management may approach the same problem with t
140
144
  const visibilityFilter = state("ALL"); // ALL, ACTIVE, COMPLETED
141
145
  const todos = state([
142
146
  { task: "Shop groceries", completed: false }
143
- /* ...more items */
147
+ /* ...and more items */
144
148
  ]);
145
149
  ```
146
150
 
147
- An update of `todos`, which is localized and isolated from other states, does not affect the components connected to `visibilityFilter` and vice versa.
151
+ An update to `todos`, which is localized and isolated from other states, does not affect components connected to `visibilityFilter` and vice versa.
148
152
 
149
- The difference might sound insignificant, but imaging every single state update could cause every selector in every component in the entire app to run again, it suggests that decentralized state model scales better for large apps. In addition, some other benefits such as code-splitting are made easier by this state model.
153
+ While the difference might seem insignificant, imagine that every small state update could cause every selector in every component across the entire app to re-run. This suggests that the decentralized state model scales better for large apps. Additionally, benefits like code-splitting are easier to implement with this state model.
150
154
 
151
- ## Why this over Zustand?
155
+ ## Why choose this over Zustand?
152
156
 
153
- - State updates localized and isolated from other irrelevant states.
154
- - No potential naming conflicts among states/actions within the big store.
157
+ - State updates are localized and isolated from other irrelevant states.
158
+ - No potential naming conflicts among states/actions within a large store.
155
159
  - No need to use a React Hook to extract actions from the store.
156
- - Actions come from outside React and no need to add them into the `useCallback/useEffect` dep array.
160
+ - Actions are external to React, eliminating the need to add them to the `useCallback/useEffect` dep array.
157
161
 
158
162
  # Recipes
159
163
 
160
- ## States should be updated immutably
164
+ ## State should be updated immutably
161
165
 
162
166
  ```js
163
167
  import { state } from "reactish-state";
@@ -185,7 +189,7 @@ Or, simply use the [immer middleware](#immer-middleware).
185
189
 
186
190
  ## Selectors are memoized
187
191
 
188
- Selector has an API that is similar to the [reselect](https://github.com/reduxjs/reselect#readme) package. You pass in one or more "input" states or selectors, and an "output" selector function that receives the extracted values and should return a derived value. The return value is memoized so that it won't cause React components to re-render even if non-primitive value is returned.
192
+ Selector has an API similar to the [reselect](https://github.com/reduxjs/reselect#readme) package. You pass in one or more 'input' states or selectors, along with an 'output' selector function that receives the extracted values and returns a derived value. The return value is memoized, ensuring that React components won’t re-render even if a non-primitive value is returned.
189
193
 
190
194
  ```js
191
195
  import { selector } from "reactish-state";
@@ -209,9 +213,11 @@ const todoStats = selector(
209
213
  );
210
214
  ```
211
215
 
216
+ The only difference between state and selector is that selectors are read-only and don’t have a `set` method.
217
+
212
218
  ## Async state updates
213
219
 
214
- Just call `set` when you're ready:
220
+ Just call `set` when your data is ready:
215
221
 
216
222
  ```js
217
223
  const todosState = state([]);
@@ -233,9 +239,9 @@ const todosState = state([], (set) => ({
233
239
  }));
234
240
  ```
235
241
 
236
- ## Accessing other state or selector inside actions
242
+ ## Accessing other state or selectors inside actions
237
243
 
238
- You might not need it, but nothing stops you from reading or writing to other state inside an action.
244
+ You might not need it, but nothing prevents you from reading or writing to other state inside an action.
239
245
 
240
246
  ```js
241
247
  const inputState = state("New item");
@@ -250,7 +256,7 @@ const todosState = state(
250
256
  );
251
257
  ```
252
258
 
253
- ## Interacting with state or selector outside React
259
+ ## Interacting with state or selectors outside React
254
260
 
255
261
  ```js
256
262
  const countState = state(0);
@@ -260,27 +266,25 @@ const tripleSelector = selector(countState, (count) => count * 3);
260
266
  const count = countState.get();
261
267
  const triple = tripleSelector.get();
262
268
 
263
- // Listen to updates
269
+ // Listen for updates
264
270
  const unsub1 = countState.subscribe(() => console.log(countState.get()));
265
271
  const unsub2 = tripleSelector.subscribe(() =>
266
272
  console.log(tripleSelector.get())
267
273
  );
268
274
 
269
- // Update `countState`, will trigger both listeners
275
+ // Updating `countState` will trigger both listeners
270
276
  countState.set(10);
271
277
 
272
- // Unsubscribe listeners
278
+ // Unsubscribe from listeners
273
279
  unsub1();
274
280
  unsub2();
275
281
  ```
276
282
 
277
- The only difference between state and selector is that selectors are read-only which don't have a `set` method.
283
+ ## Destructuring actions for easier access
278
284
 
279
- ## Destructuring actions for easier reference
285
+ The `set` or actions of a state don't rely on `this` to work, so you can destructure them for easier reference.
280
286
 
281
- The `set` or actions of a state don't rely on `this` to work, thus you are free to destructure them for easier reference.
282
-
283
- _TIP_: destructure the actions outside React components so that you don't need to add them into the `useCallback/useEffect` dependency array.
287
+ _TIP_: Destructure the actions outside of React components to avoid adding them to the `useCallback/useEffect` dependency array.
284
288
 
285
289
  ```jsx
286
290
  import { state, useSnapshot } from "reactish-state";
@@ -303,9 +307,9 @@ const Example = () => {
303
307
  };
304
308
  ```
305
309
 
306
- ## Selector that depends on props or local states
310
+ ## Selector that depends on props or local state
307
311
 
308
- The `selector` function allows us to create reusable derived states outside React components. In contrast, component-scoped derived states which depend on props or local states can be created by the `useSelector` hook.
312
+ The `selector` function allows us to create reusable derived states outside of React components. In contrast, component-scoped derived states that depend on props or local state can be created using the `useSelector` hook.
309
313
 
310
314
  ```jsx
311
315
  import { state, useSelector } from "reactish-state";
@@ -329,21 +333,21 @@ const FilteredTodoList = ({ filter = "ALL" }) => {
329
333
  ],
330
334
  [filter]
331
335
  );
332
- // Render filtered todos...
336
+ // Render the filtered todos...
333
337
  };
334
338
  ```
335
339
 
336
- The second parameter of `useSelector` is a dependency array (similar to React's `useMemo` hook), in which you can specify what props or local states the selector depends on. In the above example, `FilteredTodoList` component will re-render only if the global `todosState` state or local `filter` prop have been updated.
340
+ The second parameter of `useSelector` is a dependency array (similar to React's `useMemo` hook), where you can specify which props or local state the selector depends on. In the example above, the `FilteredTodoList` component will re-render only if the global `todosState` or the local `filter` prop is updated.
337
341
 
338
342
  ### Linting the dependency array of useSelector
339
343
 
340
- You can take advantage of the [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) package to lint the dependency array of `useSelector`. Add the following configuration into your ESLint config file:
344
+ You can take advantage of the [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) package to lint the dependency array of `useSelector`. Simply add the following configuration to your ESLint config file:
341
345
 
342
346
  ```json
343
347
  {
344
348
  "rules": {
345
349
  "react-hooks/exhaustive-deps": [
346
- "warn",
350
+ "error",
347
351
  {
348
352
  "additionalHooks": "useSelector"
349
353
  }
@@ -376,7 +380,7 @@ console.log(countState.get()); // Print 3
376
380
 
377
381
  ## Middleware
378
382
 
379
- You can enhance the functionalities of state with middleware. Instead of using the `state` export, you use the `createState` export from the library. Middleware is a function which receives `set`, `get` and `subscribe` and should return a new set function.
383
+ You can enhance the functionality of state with middleware. Instead of using the `state` export, use the `createState` export from the library. Middleware is a function that receives `set`, `get`, and `subscribe`, and should return a new set function.
380
384
 
381
385
  ```js
382
386
  import { createState } from "reactish-state";
@@ -386,12 +390,12 @@ const state = createState({
386
390
  ({ set, get }) =>
387
391
  (...args) => {
388
392
  set(...args);
389
- // Log state every time after calling `set`
393
+ // Log the state every time after calling `set`
390
394
  console.log("New state", get());
391
395
  }
392
396
  });
393
397
 
394
- // Now the `state` function has wired up a middleware
398
+ // Now the `state` function has middleware wired up
395
399
  const countState = state(0, (set) => ({
396
400
  increase: () => set((count) => count + 1)
397
401
  }));
@@ -400,21 +404,21 @@ countState.set(99); // Print "New state 99"
400
404
  countState.actions.increase(); // Print "New state 100"
401
405
 
402
406
  // The same `state` function can be reused,
403
- // thus you don't need to set up the middleware again
407
+ // so you don't need to set up the middleware again
404
408
  const filterState = state("ALL");
405
409
  filterState.set("COMPLETED"); // Print "New state 'COMPLETED'"
406
410
  ```
407
411
 
408
412
  ## Persist middleware
409
413
 
410
- You can save state in browser storage with the `persist` middleware.
414
+ You can save the state to browser storage using the `persist` middleware.
411
415
 
412
416
  ```js
413
417
  import { createState } from "reactish-state";
414
418
  import { persist } from "reactish-state/middleware";
415
419
 
416
420
  // Create the persist middleware,
417
- // you can optionally provide a `prefix` prepended to the keys in storage
421
+ // optionally provide a `prefix` to prepend to the keys in storage
418
422
  const persistMiddleware = persist({ prefix: "myApp-" });
419
423
  const state = createState({ middleware: persistMiddleware });
420
424
 
@@ -423,20 +427,20 @@ const countState = state(
423
427
  (set) => ({
424
428
  increase: () => set((count) => count + 1)
425
429
  }),
426
- { key: "count" } // In the third parameter, give each state a unique key
430
+ { key: "count" } // In the third parameter, assign each state a unique key
427
431
  );
428
432
  const filterState = state("ALL", null, { key: "filter" });
429
433
 
430
434
  // Hydrate all the states created with this middleware from storage
431
435
  useEffect(() => {
432
- // Call `hydrate` in an useEffect to avoid client-side mismatch
436
+ // Call `hydrate` in a `useEffect` to avoid client-side mismatch,
433
437
  // if React components are also server-rendered
434
438
  persistMiddleware.hydrate();
435
439
  }, []);
436
- // You can add the `useEffect` once into your root component
440
+ // You can add the `useEffect` once in your root component
437
441
  ```
438
442
 
439
- By default `localStorage` is used to persist states. You can change it to `sessionStorage` or other implementations using the `getStorage` option.
443
+ By default, `localStorage` is used to persist states. You can switch to `sessionStorage` or other implementations by using the `getStorage` option.
440
444
 
441
445
  ```js
442
446
  const persistMiddleware = persist({ getStorage: () => sessionStorage });
@@ -444,7 +448,7 @@ const persistMiddleware = persist({ getStorage: () => sessionStorage });
444
448
 
445
449
  ## Immer middleware
446
450
 
447
- You can mutably update state with the `immer` middleware.
451
+ You can update state mutably using the `immer` middleware.
448
452
 
449
453
  ```js
450
454
  import { createState } from "reactish-state";
@@ -457,8 +461,8 @@ const todos = state([], (set) => ({
457
461
  add: (task) =>
458
462
  set((todos) => {
459
463
  todos.push({ id: todoId++, task, completed: false });
460
- // Need to return the draft state for correct typing in TypeScript code
461
- // return todos;
464
+ // Return the draft state for correct typing in TypeScript
465
+ return todos;
462
466
  }),
463
467
 
464
468
  toggle: (id) =>
@@ -468,14 +472,14 @@ const todos = state([], (set) => ({
468
472
  })
469
473
  }));
470
474
 
471
- // Using the actions
475
+ // Use the actions
472
476
  todos.actions.add("Shop groceries");
473
477
  todos.actions.toggle(1);
474
478
  ```
475
479
 
476
480
  ## Redux devtools middleware
477
481
 
478
- Individual state will be combined into one big object in the Redux devtools for easy inspection.
482
+ This middleware provides integration with the Redux DevTools browser extension. Individual states are combined into a single object in Redux DevTools for easy inspection.
479
483
 
480
484
  ```js
481
485
  import { createState } from "reactish-state";
@@ -491,7 +495,7 @@ const todos = state(
491
495
  (todos) => {
492
496
  /* Add todo */
493
497
  },
494
- // Log action type in the second parameter of `set`
498
+ // Log the action type in the second parameter of `set`
495
499
  "todo/add"
496
500
  ),
497
501
  toggle: (id) =>
@@ -499,21 +503,21 @@ const todos = state(
499
503
  (todos) => {
500
504
  /* Toggle todo */
501
505
  },
502
- // You can also log action type along with its payload
506
+ // You can also log the action type along with its payload
503
507
  { type: "todo/toggle", id }
504
508
  )
505
509
  }),
506
- // Similar to the persist middleware, give each state a unique key
510
+ // Similar to the persist middleware, assign each state a unique key
507
511
  { key: "todos" }
508
512
  );
509
513
 
510
- // `todos` and `filter` will be combined into one state in the Redux devtools
514
+ // `todos` and `filter` will be combined into a single object in Redux DevTools
511
515
  const filter = state("ALL", null, { key: "filter" });
512
516
  ```
513
517
 
514
518
  ## Using multiple middleware
515
519
 
516
- Middleware is chain-able. You can use the `applyMiddleware` utility to chain multiple middleware and supply the result to `createState`.
520
+ Middleware is chainable. You can use the `applyMiddleware` utility to chain multiple middleware and pass the result to `createState`.
517
521
 
518
522
  ```js
519
523
  import { applyMiddleware } from "reactish-state/middleware";
@@ -535,11 +539,11 @@ const visibilityFilter = persistState("ALL"); // Will be persisted
535
539
  const todos = immerState([]); // Can be mutated
536
540
  ```
537
541
 
538
- It also helps eliminate the need for implementing whitelist/blacklist in a persist middleware.
542
+ This also eliminates the need to implement a whitelist or blacklist in the persist middleware.
539
543
 
540
544
  ## Plugins
541
545
 
542
- While the middleware is used to enhance state, you can hook into selectors using the plugins. The main difference is that plugins don't return a `set` function because selectors are read-only. Similarly, you use the `createSelector` export from the library rather than `selector`.
546
+ While middleware enhances state, plugins allow you to hook into selectors. The key difference is that plugins dont return a `set` function, as selectors are read-only. Similarly, you use the `createSelector` export from the library instead of `selector`.
543
547
 
544
548
  ```js
545
549
  import { state, createSelector } from "reactish-state";
@@ -547,8 +551,8 @@ import { state, createSelector } from "reactish-state";
547
551
  const selector = createSelector({
548
552
  plugin: ({ get, subscribe }, config) => {
549
553
  subscribe(() => {
550
- // Log selector value every time after it has changed
551
- // `config` can hold contextual data from a selector
554
+ // Log the selector value every time it changes
555
+ // `config` can hold contextual data from the selector
552
556
  console.log(`${config?.key} selector:`, get());
553
557
  });
554
558
  }
@@ -558,7 +562,7 @@ const countState = state(0);
558
562
  const doubleSelector = selector(
559
563
  countState,
560
564
  (count) => count * 2,
561
- // Provide contextual data in the last parameter to identity selector
565
+ // Provide contextual data in the last parameter to identify the selector
562
566
  {
563
567
  key: "double"
564
568
  }
@@ -567,23 +571,35 @@ const squareSelector = selector(countState, (count) => count * count, {
567
571
  key: "square"
568
572
  });
569
573
 
570
- countState.set(5); // Will log - double selector: 10, square selector: 25
574
+ countState.set(5); // Logs - double selector: 10, square selector: 25
571
575
  ```
572
576
 
573
577
  Likewise, there is an `applyPlugin` function for applying multiple plugins.
574
578
 
575
579
  ## Redux devtools plugin
576
580
 
577
- Individual selector will be combined into one big object in the Redux devtools for easy inspection.
581
+ Individual selectors are combined into a single object in Redux DevTools for easy inspection.
578
582
 
579
583
  ```js
580
584
  import { createSelector } from "reactish-state";
581
585
  import { reduxDevtools } from "reactish-state/plugin";
582
586
 
583
587
  const selector = createSelector({ plugin: reduxDevtools() });
584
- // Then use the `selector` as always...
588
+ // Then use the `selector` as usual...
589
+ ```
590
+
591
+ # React 16/17 setup
592
+
593
+ 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.
594
+
595
+ ```js
596
+ import { setReactShim } from "reactish-state";
597
+ import { reactShim } from "reactish-state/shim";
598
+ setReactShim(reactShim);
585
599
  ```
586
600
 
601
+ 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
+
587
603
  # Examples
588
604
 
589
605
  - Counter – [sandbox](https://codesandbox.io/p/sandbox/reactish-counter-z42qt7) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/counter)
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var state = require('./vanilla/state.cjs');
4
+ var selector = require('./vanilla/selector.cjs');
5
+ var useSnapshot = require('./react/useSnapshot.cjs');
6
+ var useSelector = require('./react/useSelector.cjs');
7
+ var shim = require('./react/shim.cjs');
8
+
9
+
10
+
11
+ exports.createState = state.createState;
12
+ exports.state = state.state;
13
+ exports.createSelector = selector.createSelector;
14
+ exports.selector = selector.selector;
15
+ exports.useSnapshot = useSnapshot.useSnapshot;
16
+ exports.useSelector = useSelector.useSelector;
17
+ exports.setReactShim = shim.setReactShim;
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+
5
+ exports.useSyncExternalStore = React.useSyncExternalStore;
6
+ const setReactShim = ([shim]) => {
7
+ exports.useSyncExternalStore = shim;
8
+ };
9
+
10
+ exports.setReactShim = setReactShim;
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var utils = require('../utils.cjs');
5
+ var useSnapshot = require('./useSnapshot.cjs');
6
+
7
+ const useSelector = (selectorParamFactory, deps) => {
8
+ const items = selectorParamFactory();
9
+ const cutoff = items.length - 1;
10
+ const selectorFunc = items[cutoff];
11
+ items.length = cutoff;
12
+ const [context] = React.useState(() => ({
13
+ sub: utils.createSubscriber(items)
14
+ }));
15
+ const get = () => {
16
+ const {
17
+ cache
18
+ } = context;
19
+ const reactishValues = utils.getReactishValues(items);
20
+ const args = reactishValues.concat(deps || selectorFunc);
21
+ if (cache && utils.isEqual(args, cache.args)) return cache.val;
22
+ const val = selectorFunc(...reactishValues);
23
+ context.cache = {
24
+ args,
25
+ val
26
+ };
27
+ return val;
28
+ };
29
+ return useSnapshot.useSnapshot({
30
+ get,
31
+ subscribe: context.sub
32
+ });
33
+ };
34
+
35
+ exports.useSelector = useSelector;
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ var shim = require('./shim.cjs');
4
+
5
+ const useSnapshot = ({
6
+ subscribe,
7
+ get
8
+ }) => {
9
+ if (process.env.NODE_ENV !== 'production' && !shim.useSyncExternalStore) {
10
+ throw new Error('[reactish-state] Shim setup is required for React 16/17.');
11
+ }
12
+ return shim.useSyncExternalStore(subscribe, get, get);
13
+ };
14
+
15
+ exports.useSnapshot = useSnapshot;
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ const isEqual = (args1, args2) => {
4
+ for (let i = 0; i < args1.length; i++) {
5
+ if (!Object.is(args1[i], args2[i])) return false;
6
+ }
7
+ return true;
8
+ };
9
+ const createSubscriber = items => listener => {
10
+ const unsubscribers = items.map(item => item.subscribe(listener));
11
+ return () => unsubscribers.forEach(unsubscribe => unsubscribe());
12
+ };
13
+ const getReactishValues = items => items.map(item => item.get());
14
+
15
+ exports.createSubscriber = createSubscriber;
16
+ exports.getReactishValues = getReactishValues;
17
+ exports.isEqual = isEqual;
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ var utils = require('../utils.cjs');
4
+
5
+ const createSelector = ({
6
+ plugin
7
+ } = {}) => (...items) => {
8
+ const {
9
+ length
10
+ } = items;
11
+ const cutoff = typeof items[length - 1] === 'function' ? length - 1 : length - 2;
12
+ const selectorFunc = items[cutoff];
13
+ const config = items[cutoff + 1];
14
+ items.length = cutoff;
15
+ let cache;
16
+ const selector = {
17
+ get: () => {
18
+ const args = utils.getReactishValues(items);
19
+ if (cache && utils.isEqual(args, cache.args)) return cache.val;
20
+ const val = selectorFunc(...args);
21
+ cache = {
22
+ args,
23
+ val
24
+ };
25
+ return val;
26
+ },
27
+ subscribe: utils.createSubscriber(items)
28
+ };
29
+ plugin?.(selector, config);
30
+ return selector;
31
+ };
32
+ const selector = /*#__PURE__*/createSelector();
33
+
34
+ exports.createSelector = createSelector;
35
+ exports.selector = selector;