state-decorator 7.0.0-beta.1 → 7.0.0-beta.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 +263 -168
- package/es/helpers.d.ts +20 -2
- package/es/helpers.js +1 -1
- package/es/helpers.js.map +1 -1
- package/es/impl.d.ts +3 -2
- package/es/impl.js +1 -1
- package/es/index.d.ts +19 -7
- package/es/index.js +1 -1
- package/es/index.js.map +1 -1
- package/es/types.d.ts +11 -2
- package/es/v5.js +1 -1
- package/es/v5.js.map +1 -1
- package/helpers.d.ts +20 -2
- package/helpers.js +1 -1
- package/helpers.js.map +1 -1
- package/impl.d.ts +3 -2
- package/impl.js +1 -1
- package/index.d.ts +19 -7
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/types.d.ts +11 -2
- package/v5.js +1 -1
- package/v5.js.map +1 -1
package/README.md
CHANGED
|
@@ -58,8 +58,7 @@ yarn add state-decorator
|
|
|
58
58
|
### Example
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
61
|
-
import
|
|
62
|
-
import { useLocalStore, StoreActions } from 'state-decorator';
|
|
61
|
+
import { useLocalStore, StoreConfig } from "state-decorator";
|
|
63
62
|
|
|
64
63
|
// Typings
|
|
65
64
|
|
|
@@ -84,45 +83,48 @@ type Props = {
|
|
|
84
83
|
};
|
|
85
84
|
|
|
86
85
|
// Initial state & actions
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
86
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
87
|
+
getInitialState: () => ({
|
|
88
|
+
selectedUserId: null,
|
|
89
|
+
user: null,
|
|
90
|
+
}),
|
|
91
|
+
|
|
92
|
+
actions: {
|
|
93
|
+
// synchronous action (simple form)
|
|
94
|
+
selectUser: ({ args: [id] }) => ({ selectedUserId: id }),
|
|
95
|
+
|
|
96
|
+
// a synchronous action with side effects
|
|
97
|
+
selectAndLoadUser: {
|
|
98
|
+
// effects contain only immutable state changes
|
|
99
|
+
effects: ({ args: [id] }) => ({ selectedUserId: id }),
|
|
100
|
+
// side effects (ie. other effects than state changes), using "actions" alias
|
|
101
|
+
sideEffects: ({ a }) => {
|
|
102
|
+
a.loadUser();
|
|
103
|
+
},
|
|
104
104
|
},
|
|
105
|
-
},
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
106
|
+
// asynchronous action
|
|
107
|
+
loadUser: {
|
|
108
|
+
// promise provider, can use action context (ctx) to get current state, args, props etc.
|
|
109
|
+
getPromise: (ctx) =>
|
|
110
|
+
Promise.resolve({ firstName: "John", lastName: "Doe" }),
|
|
111
|
+
// state changes
|
|
112
|
+
effects: ({ res }) => ({ user: res }),
|
|
113
|
+
// state changes if error
|
|
114
|
+
errorEffects: ({ error }) => ({ user: null }),
|
|
115
|
+
// side effects here
|
|
116
|
+
sideEffects: ({ res, actions }) => {},
|
|
117
|
+
// side effects if error
|
|
118
|
+
errorSideEffects: ({ err, actions }) => {},
|
|
119
|
+
// many more options available here, read the doc!
|
|
120
|
+
},
|
|
120
121
|
},
|
|
121
122
|
};
|
|
122
123
|
|
|
124
|
+
|
|
123
125
|
// Bind to react component and use state/actions
|
|
124
126
|
export function App(props: Props) => {
|
|
125
|
-
const { state, actions } = useLocalStore(
|
|
127
|
+
const { state, actions } = useLocalStore(config);
|
|
126
128
|
return (
|
|
127
129
|
<div>
|
|
128
130
|
{state.user && (
|
|
@@ -160,12 +162,13 @@ export function App(props: Props) => {
|
|
|
160
162
|
The initial state is build using a function provided to the store.
|
|
161
163
|
|
|
162
164
|
```typescript
|
|
163
|
-
|
|
164
|
-
counter: 0,
|
|
165
|
-
}
|
|
165
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
166
|
+
getInitialState: (p) => ({ counter: 0 }),
|
|
167
|
+
actions: {},
|
|
168
|
+
};
|
|
166
169
|
|
|
167
170
|
export const CounterContainer = () => {
|
|
168
|
-
const { state, actions } = useLocalStore(
|
|
171
|
+
const { state, actions } = useLocalStore(config);
|
|
169
172
|
return <CounterView {...state} {...actions} />;
|
|
170
173
|
};
|
|
171
174
|
```
|
|
@@ -185,6 +188,7 @@ The action context contain all the data needed to implement the actions. It cont
|
|
|
185
188
|
| derived | ds | everywhere | The derived state (from props/state) |
|
|
186
189
|
| promiseId | | asynchronous actions callbacks | Parallel asynchronous actions promise identifier |
|
|
187
190
|
| notifyWarning | | side effects | Injected function if set at store or global level |
|
|
191
|
+
| clearError | | side effects | Clear an error from ErrorMap |
|
|
188
192
|
| indices | | onPropsChange | Indices in the getDeps array of changed props |
|
|
189
193
|
| isInit | | onPropsChange | Whether the onPropsCHange is executed during init phase |
|
|
190
194
|
|
|
@@ -205,8 +209,10 @@ action: (ctx) => ctx.state, // changes to be merged to current state
|
|
|
205
209
|
Example:
|
|
206
210
|
|
|
207
211
|
```typescript
|
|
208
|
-
const
|
|
209
|
-
|
|
212
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
213
|
+
actions: {
|
|
214
|
+
setText: ({ args: [newText] }) => ({ text: newText }),
|
|
215
|
+
},
|
|
210
216
|
};
|
|
211
217
|
```
|
|
212
218
|
|
|
@@ -229,14 +235,16 @@ action: {
|
|
|
229
235
|
Example:
|
|
230
236
|
|
|
231
237
|
```typescript
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
a
|
|
238
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
239
|
+
actions: {
|
|
240
|
+
setText: {
|
|
241
|
+
effects: ({ args: [newText] }) => ({ text: newText }),
|
|
242
|
+
// use either debounceTimeout or debounceSideEffectsTimeout
|
|
243
|
+
// debounceTimeout: 5000,
|
|
244
|
+
debounceSideEffectsTimeout: 5000,
|
|
245
|
+
sideEffects: ({ a }) => {
|
|
246
|
+
a.saveDocument();
|
|
247
|
+
},
|
|
240
248
|
},
|
|
241
249
|
},
|
|
242
250
|
};
|
|
@@ -278,14 +286,16 @@ action: {
|
|
|
278
286
|
Example:
|
|
279
287
|
|
|
280
288
|
```typescript
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
a
|
|
289
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
290
|
+
actions: {
|
|
291
|
+
loadList: {
|
|
292
|
+
getPromise: () => fetch('https://myapi/myservices').then((res) => (res.ok ? res.json() : Promise.reject())),
|
|
293
|
+
effects: ({ res }) => ({ list: res }),
|
|
294
|
+
sideEffects: ({ a }) => {
|
|
295
|
+
a.otherAction();
|
|
296
|
+
},
|
|
297
|
+
getErrorMessage: () => 'Cannot load list',
|
|
287
298
|
},
|
|
288
|
-
getErrorMessage: () => 'Cannot load list',
|
|
289
299
|
},
|
|
290
300
|
};
|
|
291
301
|
```
|
|
@@ -383,11 +393,13 @@ const store = useLocalStore(getInitialState, actionsImpl, props, {
|
|
|
383
393
|
|
|
384
394
|
```typescript
|
|
385
395
|
// action implementation
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
396
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
397
|
+
actions: {
|
|
398
|
+
loadList: {
|
|
399
|
+
getPromise: () => new Promise((_, reject) => setTimeout(reject, 500, new Error('Too bad'))),
|
|
400
|
+
getSuccessMessage: () => 'success!',
|
|
401
|
+
getErrorMessage: ({ err }) => `Error: ${err.message}`,
|
|
402
|
+
},
|
|
391
403
|
},
|
|
392
404
|
};
|
|
393
405
|
```
|
|
@@ -448,6 +460,8 @@ if (errorMap.action1 instanceof MyCustomError){
|
|
|
448
460
|
|
|
449
461
|
Recipe: When only using the **errorMap** to manage errors (ie. there's no effects nor message and so on), the error is considered _not managed_ resulting in a rejected promise and global **asyncErrorHandler** called with _managed_ flag set to false. In that case, set **isErrorManaged** to `true` to specify that the error is _managed_ externally.
|
|
450
462
|
|
|
463
|
+
The error map is updated when the action is called again, or call **clearError** in a side effect.
|
|
464
|
+
|
|
451
465
|
## <a name="ConflictingActions"></a>Conflicting actions
|
|
452
466
|
|
|
453
467
|
The StateDecorator is managing the asynchronous action calls one at a time (default) or in parallel.
|
|
@@ -502,10 +516,20 @@ setGlobalConfig({
|
|
|
502
516
|
On a specific store:
|
|
503
517
|
|
|
504
518
|
```typescript
|
|
505
|
-
import { createStore } from 'state-decorator';
|
|
519
|
+
import { createStore, StoreConfig } from 'state-decorator';
|
|
506
520
|
import { optimisticActions } from 'state-decorator/middlewares';
|
|
507
521
|
|
|
508
|
-
const
|
|
522
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
523
|
+
getInitialState: () => ({
|
|
524
|
+
/* ... */
|
|
525
|
+
}),
|
|
526
|
+
actions: {
|
|
527
|
+
/* ... */
|
|
528
|
+
},
|
|
529
|
+
middlewares: [optimisticActions()],
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const store = createStore(config);
|
|
509
533
|
```
|
|
510
534
|
|
|
511
535
|
An optimistic action assumes that the action will, most of the time, succeed. So it will apply the effects as soon as the asynchronous action is called (as opposite to the regular effects which are applied when the promise is resolved).
|
|
@@ -523,11 +547,13 @@ The undo strategy is the following:
|
|
|
523
547
|
Example:
|
|
524
548
|
|
|
525
549
|
```typescript
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
550
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
551
|
+
actions: {
|
|
552
|
+
deleteItem: {
|
|
553
|
+
getPromise: ([id]) => new Promise((resolve) => setTimeout(resolve, 500)),
|
|
554
|
+
optimisticEffects: ({ args: [id] }) => ({ list: list.filter((item) => item.id === id) }),
|
|
555
|
+
errorEffects: ({err} => ({ err: error.message }),
|
|
556
|
+
},
|
|
531
557
|
},
|
|
532
558
|
};
|
|
533
559
|
```
|
|
@@ -551,26 +577,28 @@ Ongoing asynchronous actions can be aborted using, under the hood, the [AbortCon
|
|
|
551
577
|
3. An abort is a specific failure of an action, ie. **errorEffects** and **errorSideEffects** will be called. Use the error type and name to distinguish an aborted action from a regular failed action.
|
|
552
578
|
|
|
553
579
|
```typescript
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
580
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
581
|
+
actions: {
|
|
582
|
+
onAction: {
|
|
583
|
+
abortable: true,
|
|
584
|
+
preEffects: () => ({ isError: false, isSuccess: false, isAborted: false }),
|
|
585
|
+
getPromise: ({s, args: [willCrash] abortSignal}) =>
|
|
586
|
+
new Promise((resolve, reject) => {
|
|
587
|
+
const timeout = window.setTimeout(willCrash ? reject : resolve, 2500, willCrash ? new Error('boom') : 'result');
|
|
588
|
+
abortSignal.addEventListener('abort', () => {
|
|
589
|
+
window.clearTimeout(timeout);
|
|
590
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
591
|
+
});
|
|
592
|
+
}),
|
|
593
|
+
errorEffects: ({ err}) => (err.name === 'AbortError' ? { isAborted: true } : { isError: true }),
|
|
594
|
+
effects: () => ({ isSuccess: true }),
|
|
595
|
+
errorSideEffects: ({ err }) => {
|
|
596
|
+
if (e.name === 'AbortError') {
|
|
597
|
+
console.log('AbortError side effect');
|
|
598
|
+
} else {
|
|
599
|
+
console.log('Other error side effect');
|
|
600
|
+
}
|
|
601
|
+
},
|
|
574
602
|
},
|
|
575
603
|
},
|
|
576
604
|
};
|
|
@@ -604,28 +632,32 @@ const actions: StoreActions<State, Actions> = {
|
|
|
604
632
|
};
|
|
605
633
|
```
|
|
606
634
|
|
|
607
|
-
| Name
|
|
608
|
-
|
|
|
609
|
-
| setArgIn
|
|
610
|
-
| setTrueIn
|
|
611
|
-
| setFalseIn
|
|
612
|
-
| toggleProp
|
|
613
|
-
|
|
|
614
|
-
|
|
|
615
|
-
| setResIn
|
|
635
|
+
| Name | Purpose | Action signatue |
|
|
636
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
|
637
|
+
| setArgIn | Effect function to update a state property from action first argument | (v:T) => void |
|
|
638
|
+
| setTrueIn | Effect function to update a boolean state property to true | () => void |
|
|
639
|
+
| setFalseIn | Effect function to update a boolean state property to false | () => void |
|
|
640
|
+
| toggleProp | Effect function to toggle a boolean state property | () => void |
|
|
641
|
+
| setArgsInMap | Effect function to update a state property of type Record<string, T> using first action argument as key and second as value | (id: string, v:T) => void |
|
|
642
|
+
| setArgsInArray | Effect function to update a state property of type T[] using first action argument as index and second as value | (index: number, v:T) => void |
|
|
643
|
+
| setResIn | Effect function of asynchronous action to update a state property with the result of the promise | () => Promise<T> |
|
|
644
|
+
| setResInArray | Effect function to update a state property of type T[] using index from first action argument as index and as value the result of the promise | (index: number) => Promise <T> |
|
|
645
|
+
| setResInMap | Effect function to update a state property of type Record<string, T> using key from first action argument as index and as value the result of the promise | (id: string) => Promise <T> |
|
|
616
646
|
|
|
617
647
|
## Call actions on mount
|
|
618
648
|
|
|
619
649
|
When store is created (for example whe React component in mounted), the **onMount** option is called.
|
|
620
650
|
|
|
621
651
|
```typescript
|
|
622
|
-
import {
|
|
652
|
+
import { StoreConfig } from 'state-decorator';
|
|
623
653
|
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
654
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
655
|
+
getInitialState,
|
|
656
|
+
actions,
|
|
657
|
+
onMount: ({ p }) => {
|
|
658
|
+
p.loadParentList();
|
|
627
659
|
},
|
|
628
|
-
}
|
|
660
|
+
};
|
|
629
661
|
```
|
|
630
662
|
|
|
631
663
|
After React component is mounted using _useLocalStore_, the **onMountDeferred** option is called (in a _useEffect_).
|
|
@@ -633,13 +665,15 @@ The **onMount** is called _during_ the render of the hooks. If the onMount code
|
|
|
633
665
|
React will not update the parent state and will issue a warning.
|
|
634
666
|
|
|
635
667
|
```typescript
|
|
636
|
-
import {
|
|
668
|
+
import { StoreConfig } from 'state-decorator';
|
|
637
669
|
|
|
638
|
-
const
|
|
670
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
671
|
+
getInitialState,
|
|
672
|
+
actions,
|
|
639
673
|
onMountDeferred: ({ p }) => {
|
|
640
674
|
p.loadParentList();
|
|
641
675
|
},
|
|
642
|
-
}
|
|
676
|
+
};
|
|
643
677
|
```
|
|
644
678
|
|
|
645
679
|
If an initial action is launched each time a property changes, consider using the **onMount** flag of the **onPropsChange** entry.
|
|
@@ -649,14 +683,16 @@ If an initial action is launched each time a property changes, consider using th
|
|
|
649
683
|
When the store is destroyed (for example on React component unmounted), the **onUnmount** option is called.
|
|
650
684
|
|
|
651
685
|
```typescript
|
|
652
|
-
import {
|
|
686
|
+
import { StoreConfig } from 'state-decorator';
|
|
653
687
|
|
|
654
|
-
const
|
|
688
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
689
|
+
getInitialState,
|
|
690
|
+
actions,
|
|
655
691
|
onUnmount: ({ s, abortedActions }) => {
|
|
656
692
|
// ex: store s in local storage
|
|
657
693
|
// ex: clean cache of aborted actions
|
|
658
694
|
},
|
|
659
|
-
}
|
|
695
|
+
};
|
|
660
696
|
```
|
|
661
697
|
|
|
662
698
|
## State sharing and slices
|
|
@@ -673,7 +709,7 @@ import { useStore, useStoreSlice, StoreActions } from 'state-decorator';
|
|
|
673
709
|
|
|
674
710
|
// Create a store
|
|
675
711
|
|
|
676
|
-
export const store = createStore(
|
|
712
|
+
export const store = createStore(config);
|
|
677
713
|
|
|
678
714
|
// Bind to react component
|
|
679
715
|
|
|
@@ -710,7 +746,9 @@ export function SubComponent() {
|
|
|
710
746
|
```typescript
|
|
711
747
|
import { createStore } from 'state-decorator';
|
|
712
748
|
|
|
713
|
-
const
|
|
749
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
750
|
+
getInitialState,
|
|
751
|
+
actions,
|
|
714
752
|
onPropsChange: {
|
|
715
753
|
// list of depedencies that should trigger effects & side effects if changed
|
|
716
754
|
getDeps: (p) => [p.id],
|
|
@@ -723,7 +761,7 @@ const store = createStore(getInitialState, actionsImpl, {
|
|
|
723
761
|
// whether apply effects & side effects on mount
|
|
724
762
|
onMount: true,
|
|
725
763
|
},
|
|
726
|
-
}
|
|
764
|
+
};
|
|
727
765
|
```
|
|
728
766
|
|
|
729
767
|
## Multiple props change configuration
|
|
@@ -735,9 +773,11 @@ The store can manage several props change configurations to have a better granul
|
|
|
735
773
|
- Side effects are invoked after all effects using computed state
|
|
736
774
|
|
|
737
775
|
```typescript
|
|
738
|
-
import {
|
|
776
|
+
import { StoreConfig } from 'state-decorator';
|
|
739
777
|
|
|
740
|
-
const
|
|
778
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
779
|
+
getInitialState,
|
|
780
|
+
actions,
|
|
741
781
|
onPropsChange: [
|
|
742
782
|
{
|
|
743
783
|
getDeps: (p) => [p.id],
|
|
@@ -757,7 +797,7 @@ const store = createStore(getInitialState, actionsImpl, {
|
|
|
757
797
|
},
|
|
758
798
|
},
|
|
759
799
|
],
|
|
760
|
-
}
|
|
800
|
+
};
|
|
761
801
|
```
|
|
762
802
|
|
|
763
803
|
## OnMount
|
|
@@ -767,7 +807,11 @@ If the onMount flag is set on the props change configuration, the effects and si
|
|
|
767
807
|
It allows to trigger same actions at creation time and when the dependencies props are changed.
|
|
768
808
|
|
|
769
809
|
```typescript
|
|
770
|
-
|
|
810
|
+
import { StoreConfig } from 'state-decorator';
|
|
811
|
+
|
|
812
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
813
|
+
getInitialState,
|
|
814
|
+
actions,
|
|
771
815
|
onMount: ({ p }) => {
|
|
772
816
|
a.loadUser(p.userId);
|
|
773
817
|
},
|
|
@@ -780,11 +824,15 @@ const store = createStore(getInitialState, actionsImpl, {
|
|
|
780
824
|
},
|
|
781
825
|
},
|
|
782
826
|
],
|
|
783
|
-
}
|
|
827
|
+
};
|
|
784
828
|
|
|
785
829
|
// SAME AS
|
|
786
830
|
|
|
787
|
-
|
|
831
|
+
import { StoreConfig } from 'state-decorator';
|
|
832
|
+
|
|
833
|
+
const config: StoreConfig<State, Actions, Props> = {
|
|
834
|
+
getInitialState,
|
|
835
|
+
actions,
|
|
788
836
|
onPropsChange: [
|
|
789
837
|
{
|
|
790
838
|
getDeps: (p) => [p.userId],
|
|
@@ -795,7 +843,7 @@ const store = createStore(getInitialState, actionsImpl, {
|
|
|
795
843
|
onMount: true,
|
|
796
844
|
},
|
|
797
845
|
],
|
|
798
|
-
}
|
|
846
|
+
};
|
|
799
847
|
```
|
|
800
848
|
|
|
801
849
|
**Caution**: the effects are executed too. Use the isInit parameter is passed to the context to apply or not state change.
|
|
@@ -881,7 +929,7 @@ import { useLocalStore } from 'state-decorator';
|
|
|
881
929
|
// Create and bind to react component
|
|
882
930
|
// Each time the store state changes, a re-render is done.
|
|
883
931
|
export function Container(props: Props) {
|
|
884
|
-
const { state, actions } = useLocalStore(
|
|
932
|
+
const { state, actions } = useLocalStore(config, props);
|
|
885
933
|
return (
|
|
886
934
|
<div>
|
|
887
935
|
<Title text={state.title} />
|
|
@@ -969,7 +1017,7 @@ export const StoreContext = createContext<StoreContextProps>(null);
|
|
|
969
1017
|
// But this context is NOT refreshed if the store state is changed
|
|
970
1018
|
export function StoreContextProvider(p: { children: any; propIn: string }) {
|
|
971
1019
|
const { children, ...props } = p;
|
|
972
|
-
const store = useLocalStore(
|
|
1020
|
+
const store = useLocalStore(config, props);
|
|
973
1021
|
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
|
|
974
1022
|
}
|
|
975
1023
|
|
|
@@ -1032,23 +1080,21 @@ type DerivedState = {
|
|
|
1032
1080
|
derivedProp: number;
|
|
1033
1081
|
};
|
|
1034
1082
|
|
|
1035
|
-
const store = createStore<State, Actions, Props, DerivedState>(
|
|
1036
|
-
() => ({ value: 0 }),
|
|
1037
|
-
{
|
|
1083
|
+
const store = createStore<State, Actions, Props, DerivedState>({
|
|
1084
|
+
getInitialState: () => ({ value: 0 }),
|
|
1085
|
+
actions: {
|
|
1038
1086
|
add1: ({ s }) => ({ ...s, value: s.value + 1 }),
|
|
1039
1087
|
},
|
|
1040
|
-
{
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
get: ({ s, p }) => s.value * p.propIn,
|
|
1048
|
-
},
|
|
1088
|
+
derivedState: {
|
|
1089
|
+
derivedProp: {
|
|
1090
|
+
// get the list of dependencies to be checked to trigger the computation of the derived state
|
|
1091
|
+
// if state.value AND/OR props.prop1 changes, derivedProp is recomputed
|
|
1092
|
+
getDeps: ({ state, props }) => [state.value, props.propIn],
|
|
1093
|
+
// compute derived state from state & props (use short aliases)
|
|
1094
|
+
get: ({ s, p }) => s.value * p.propIn,
|
|
1049
1095
|
},
|
|
1050
|
-
}
|
|
1051
|
-
);
|
|
1096
|
+
},
|
|
1097
|
+
});
|
|
1052
1098
|
|
|
1053
1099
|
export function App(props: Props) {
|
|
1054
1100
|
const { state } = useStore(store, props);
|
|
@@ -1100,7 +1146,7 @@ To debug actions, add a middleware to the store.
|
|
|
1100
1146
|
import { logEffects } from 'state-decorator/middlewares';
|
|
1101
1147
|
|
|
1102
1148
|
function Container(props: Props) {
|
|
1103
|
-
const { state, actions } = useLocalStore(getInitialState,
|
|
1149
|
+
const { state, actions } = useLocalStore({ getInitialState, actions, middlewares: [logEffects()] });
|
|
1104
1150
|
return <div />;
|
|
1105
1151
|
}
|
|
1106
1152
|
```
|
|
@@ -1114,7 +1160,7 @@ function logger(...args: any[]) {
|
|
|
1114
1160
|
|
|
1115
1161
|
const logMiddleware = logEffects(logger);
|
|
1116
1162
|
|
|
1117
|
-
const store = createStore(getInitialState,
|
|
1163
|
+
const store = createStore({ getInitialState, actions, middlewares: [logMiddleware] });
|
|
1118
1164
|
```
|
|
1119
1165
|
|
|
1120
1166
|
Example:
|
|
@@ -1130,9 +1176,7 @@ Example:
|
|
|
1130
1176
|
import { logDetailedEffects } from 'state-decorator/middlewares';
|
|
1131
1177
|
|
|
1132
1178
|
function Container(props: Props) {
|
|
1133
|
-
const { state, actions } = useLocalStore(getInitialState,
|
|
1134
|
-
logDetailedEffects(),
|
|
1135
|
-
]);
|
|
1179
|
+
const { state, actions } = useLocalStore({ getInitialState, actions, middlewares: [logDetailedEffects()] });
|
|
1136
1180
|
return <div />;
|
|
1137
1181
|
}
|
|
1138
1182
|
```
|
|
@@ -1161,7 +1205,7 @@ You can connect any StateDecorator store to [Chrome Redux devtools extension](ht
|
|
|
1161
1205
|
import { devtools } from 'state-decorator/middlewares';
|
|
1162
1206
|
|
|
1163
1207
|
function Container(props: Props) {
|
|
1164
|
-
const { state, actions } = useLocalStore(getInitialState,
|
|
1208
|
+
const { state, actions } = useLocalStore({ getInitialState, actions, middlewares: [devtools()] });
|
|
1165
1209
|
return <div />;
|
|
1166
1210
|
}
|
|
1167
1211
|
```
|
|
@@ -1675,7 +1719,65 @@ TypeError: Cannot set property 'crash' of null
|
|
|
1675
1719
|
|
|
1676
1720
|
# Migration
|
|
1677
1721
|
|
|
1678
|
-
##
|
|
1722
|
+
## V6 to V7
|
|
1723
|
+
|
|
1724
|
+
### Step 1: V6 compatibility layer
|
|
1725
|
+
|
|
1726
|
+
```diff
|
|
1727
|
+
-import useLocalStore from 'state-decorator';
|
|
1728
|
+
-import { useLocalStore } from 'state-decorator';
|
|
1729
|
+
|
|
1730
|
+
+import { useLocalStoreV6 } from 'state-decorator';
|
|
1731
|
+
```
|
|
1732
|
+
|
|
1733
|
+
### Step 2: Migrate code
|
|
1734
|
+
|
|
1735
|
+
#### Config
|
|
1736
|
+
|
|
1737
|
+
```diff
|
|
1738
|
+
- const getInitialState: (p:Props) => ({myProp: ''});
|
|
1739
|
+
-
|
|
1740
|
+
- const actions: StoreActions<State, Actions, Props> = {
|
|
1741
|
+
- myAction: ({s, args: [v]}) => ({ ...s, myProp: v })
|
|
1742
|
+
-}
|
|
1743
|
+
-
|
|
1744
|
+
- const options: StoreOptions<State, Action, Props> = {
|
|
1745
|
+
- onMount: () => {},
|
|
1746
|
+
- onPropsChange: {
|
|
1747
|
+
- getDeps: () => [],
|
|
1748
|
+
- effects: ({s, p}) => ({ ...s, myProp: p.prop }),
|
|
1749
|
+
- }
|
|
1750
|
+
- };
|
|
1751
|
+
-
|
|
1752
|
+
- function MyComponent(p:Props) {
|
|
1753
|
+
- const {state, actions} = useLocalStore(getInitialState, actions, props, options, [logEffects()]);
|
|
1754
|
+
- return <div>{myProp}</div>;
|
|
1755
|
+
- }
|
|
1756
|
+
|
|
1757
|
+
+ const config:StoreConfig<State, Actions, Props> = {
|
|
1758
|
+
+ getInitialState: () => ({ myProp: '' }),
|
|
1759
|
+
+ actions: {
|
|
1760
|
+
+ myAction: ({args: [v]}) => ({ myProp: v})
|
|
1761
|
+
+ // or
|
|
1762
|
+
+ myAction: setArgIn('myProp'),
|
|
1763
|
+
+ },
|
|
1764
|
+
+ onMount: () => {},
|
|
1765
|
+
+ onPropsChange: {
|
|
1766
|
+
+ getDeps: () => [],
|
|
1767
|
+
+ effects: ({ p }) => ({ myProp: p.prop })
|
|
1768
|
+
+ },
|
|
1769
|
+
+ middlewares: [logEffects()],
|
|
1770
|
+
+ }
|
|
1771
|
+
+
|
|
1772
|
+
+ function MyComponent(p:Props) {
|
|
1773
|
+
+ const {state, actions} = useLocalStore(config);
|
|
1774
|
+
+ return <div>{myProp}</div>;
|
|
1775
|
+
+ }
|
|
1776
|
+
```
|
|
1777
|
+
|
|
1778
|
+
## V5 to V6
|
|
1779
|
+
|
|
1780
|
+
### Step 1: v5 compatibility layer
|
|
1679
1781
|
|
|
1680
1782
|
To use the new engine while still using v5 API, juste import the v5 compatibility layer.
|
|
1681
1783
|
You can new store in new code.
|
|
@@ -1696,26 +1798,26 @@ setGlobalConfig({
|
|
|
1696
1798
|
});
|
|
1697
1799
|
```
|
|
1698
1800
|
|
|
1699
|
-
|
|
1801
|
+
### Step 2: Migrate code but keep v5 tests
|
|
1700
1802
|
|
|
1701
|
-
|
|
1803
|
+
#### Code
|
|
1702
1804
|
|
|
1703
|
-
|
|
1805
|
+
##### Types
|
|
1704
1806
|
|
|
1705
1807
|
| Before | After |
|
|
1706
1808
|
| --------------------- | ------------ |
|
|
1707
1809
|
| StateDecoratorActions | StoreActions |
|
|
1708
1810
|
| StateDecoratorOptions | StoreOptions |
|
|
1709
1811
|
|
|
1710
|
-
|
|
1812
|
+
##### Optimistic actions
|
|
1711
1813
|
|
|
1712
1814
|
If you are using optimistic effects, make sure to set the **optimisticActions** middleware to your stores, see [Optimistic actions](#optimistic-actions).
|
|
1713
1815
|
|
|
1714
|
-
|
|
1816
|
+
#### Hooks
|
|
1715
1817
|
|
|
1716
1818
|
Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
1717
1819
|
|
|
1718
|
-
|
|
1820
|
+
#### Actions
|
|
1719
1821
|
|
|
1720
1822
|
- rename action properties
|
|
1721
1823
|
|
|
@@ -1756,9 +1858,9 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1756
1858
|
};
|
|
1757
1859
|
```
|
|
1758
1860
|
|
|
1759
|
-
|
|
1861
|
+
#### Options
|
|
1760
1862
|
|
|
1761
|
-
|
|
1863
|
+
##### onMount
|
|
1762
1864
|
|
|
1763
1865
|
```diff
|
|
1764
1866
|
- const options: StateDecoratorOptions<S, A, P> = {
|
|
@@ -1770,7 +1872,7 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1770
1872
|
+} ;
|
|
1771
1873
|
```
|
|
1772
1874
|
|
|
1773
|
-
|
|
1875
|
+
##### logEnabled
|
|
1774
1876
|
|
|
1775
1877
|
```diff
|
|
1776
1878
|
- function Container(props: Props) {
|
|
@@ -1786,7 +1888,7 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1786
1888
|
+ }
|
|
1787
1889
|
```
|
|
1788
1890
|
|
|
1789
|
-
|
|
1891
|
+
##### Props changes
|
|
1790
1892
|
|
|
1791
1893
|
```diff
|
|
1792
1894
|
- const options: StateDecoratorOptions<S, A, P> = {
|
|
@@ -1808,7 +1910,7 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1808
1910
|
+} ;
|
|
1809
1911
|
```
|
|
1810
1912
|
|
|
1811
|
-
|
|
1913
|
+
#### Reuse v5 tests with v6 actions
|
|
1812
1914
|
|
|
1813
1915
|
```diff
|
|
1814
1916
|
- import { testSyncAction, testAsyncAction } from 'state-decorator';
|
|
@@ -1816,7 +1918,7 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1816
1918
|
+ import { testV6SyncAction, testV6AsyncAction } from 'state-decorator/v5_test';
|
|
1817
1919
|
```
|
|
1818
1920
|
|
|
1819
|
-
|
|
1921
|
+
### Step 3 (optional): Migrate tests
|
|
1820
1922
|
|
|
1821
1923
|
🎊 Congratulations! 🎊
|
|
1822
1924
|
|
|
@@ -1864,34 +1966,27 @@ You can add this snippet to quickly create a stateful functional component using
|
|
|
1864
1966
|
"import React from 'react';",
|
|
1865
1967
|
"import { useStore, StoreActions, LoadingProps } from 'state-decorator';",
|
|
1866
1968
|
"",
|
|
1867
|
-
"export type
|
|
1868
|
-
"",
|
|
1869
|
-
"export type $1State = {};",
|
|
1969
|
+
"export type Props = {};",
|
|
1870
1970
|
"",
|
|
1871
|
-
"export type
|
|
1971
|
+
"export type State = {};",
|
|
1872
1972
|
"",
|
|
1873
|
-
"type
|
|
1874
|
-
"type State = $1State;",
|
|
1875
|
-
"type Actions = $1Actions;",
|
|
1973
|
+
"export type Actions = {};",
|
|
1876
1974
|
"",
|
|
1877
1975
|
"type ViewProps = Props & Actions & State & Pick<LoadingProps<Actions>, 'loadingMap'>;",
|
|
1878
1976
|
"",
|
|
1879
|
-
"export
|
|
1880
|
-
"
|
|
1977
|
+
"export const storeConfig: StoreConfig<State, Actions, Props> = {",
|
|
1978
|
+
" getInitialState: (p:$1Props) => ({",
|
|
1979
|
+
" }),",
|
|
1980
|
+
" actions: {",
|
|
1981
|
+
" }",
|
|
1881
1982
|
"};",
|
|
1882
1983
|
"",
|
|
1883
1984
|
"export function $1View(p: ViewProps) {",
|
|
1884
1985
|
" return (<div>[$1View]</div>);",
|
|
1885
1986
|
"}",
|
|
1886
1987
|
"",
|
|
1887
|
-
"export const actions$1: StoreActions<State, Actions, Props> = {};",
|
|
1888
|
-
"",
|
|
1889
|
-
"export function onMount(actions:$1Actions) {}",
|
|
1890
|
-
"",
|
|
1891
|
-
"const store = createStore(getInitialState, actions$1, { onMount });",
|
|
1892
|
-
"",
|
|
1893
1988
|
"export default React.memo(function $1(p: $1Props) {",
|
|
1894
|
-
" const { state:s, actions:a, loadingMap } =
|
|
1989
|
+
" const { state:s, actions:a, loadingMap } = useLocalStore(config, p);",
|
|
1895
1990
|
" return <$1View {...p} {...s} {...a} loadingMap={loadingMap} />;",
|
|
1896
1991
|
"});"
|
|
1897
1992
|
],
|