reactish-state 1.0.0-alpha.0 → 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.
- package/README.md +100 -84
- package/package.json +3 -2
- package/shim/index.d.ts +1 -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
|
+
[](https://www.npmjs.com/package/reactish-state) [](https://bundlephobia.com/package/reactish-state)
|
|
4
6
|
|
|
5
7
|
## ✨Highlights✨
|
|
6
8
|
|
|
7
9
|
- Decentralized state management
|
|
8
|
-
-
|
|
9
|
-
- No need
|
|
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
|
-
-
|
|
15
|
-
- Support Redux dev tools via middleware
|
|
16
|
-
- [
|
|
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
|
-
|
|
22
|
+
```bash
|
|
23
|
+
npm install reactish-state
|
|
24
|
+
```
|
|
21
25
|
|
|
22
26
|
## Quick start
|
|
23
27
|
|
|
24
|
-
### We begin by creating some
|
|
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
|
-
//
|
|
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
|
-
//
|
|
58
|
+
// Use the actions
|
|
55
59
|
countState.actions.increase();
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
### `selector` can create derived
|
|
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
|
|
78
|
+
A selector will re-compute only when one of the states it depends on has changed.
|
|
75
79
|
|
|
76
|
-
### Use the state and
|
|
80
|
+
### Use the state and selectors in your React components
|
|
77
81
|
|
|
78
|
-
You can read state and
|
|
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
|
|
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
|
-
|
|
110
|
+
State management solutions in the React ecosystem have popularized two state models:
|
|
107
111
|
|
|
108
|
-
- **Centralized**: a single store that combines entire app
|
|
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**:
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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
|
|
242
|
+
## Accessing other state or selectors inside actions
|
|
237
243
|
|
|
238
|
-
You might not need it, but nothing
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
283
|
+
## Destructuring actions for easier access
|
|
278
284
|
|
|
279
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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),
|
|
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`.
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
461
|
-
|
|
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
|
-
//
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
546
|
+
While middleware enhances state, plugins allow you to hook into selectors. The key difference is that plugins don’t 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
|
|
551
|
-
// `config` can hold contextual data from
|
|
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
|
|
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); //
|
|
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
|
|
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
|
|
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)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactish-state",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
4
|
"description": "Simple, decentralized state management for React.",
|
|
5
5
|
"author": "Zheng Song",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"dist/",
|
|
18
18
|
"types/",
|
|
19
19
|
"middleware/",
|
|
20
|
-
"plugin/"
|
|
20
|
+
"plugin/",
|
|
21
|
+
"shim/"
|
|
21
22
|
],
|
|
22
23
|
"keywords": [
|
|
23
24
|
"react",
|
package/shim/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../types/shim';
|