state-decorator 7.0.0-beta.1 → 7.0.0-beta.4
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 +265 -169
- 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 +5 -4
- package/es/impl.js +1 -1
- package/es/index.d.ts +21 -10
- package/es/index.js +1 -1
- package/es/index.js.map +1 -1
- package/es/test.d.ts +4 -2
- package/es/test.js +1 -1
- package/es/test.js.map +1 -1
- package/es/types.d.ts +12 -2
- package/es/v5.js +1 -1
- package/es/v5.js.map +1 -1
- package/es/v5_test.js +1 -1
- package/helpers.d.ts +20 -2
- package/helpers.js +1 -1
- package/helpers.js.map +1 -1
- package/impl.d.ts +5 -4
- package/impl.js +1 -1
- package/index.d.ts +21 -10
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/test.d.ts +4 -2
- package/test.js +1 -1
- package/test.js.map +1 -1
- package/types.d.ts +12 -2
- package/v5.js +1 -1
- package/v5.js.map +1 -1
- package/v5_test.js +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
|
```
|
|
@@ -1203,7 +1247,8 @@ describe('Todo', () => {
|
|
|
1203
1247
|
// this store can be shared accross tests because it is immutable
|
|
1204
1248
|
const store = createMockFromStore(todoStore)
|
|
1205
1249
|
// or
|
|
1206
|
-
// const store = createMockStore(
|
|
1250
|
+
// const store = createMockStore(storeConfig, props)
|
|
1251
|
+
|
|
1207
1252
|
// initial state / props can be set in full form or partial form
|
|
1208
1253
|
.setPartialState({
|
|
1209
1254
|
idCount: 3,
|
|
@@ -1675,7 +1720,65 @@ TypeError: Cannot set property 'crash' of null
|
|
|
1675
1720
|
|
|
1676
1721
|
# Migration
|
|
1677
1722
|
|
|
1678
|
-
##
|
|
1723
|
+
## V6 to V7
|
|
1724
|
+
|
|
1725
|
+
### Step 1: V6 compatibility layer
|
|
1726
|
+
|
|
1727
|
+
```diff
|
|
1728
|
+
-import useLocalStore from 'state-decorator';
|
|
1729
|
+
-import { useLocalStore } from 'state-decorator';
|
|
1730
|
+
|
|
1731
|
+
+import { useLocalStoreV6 } from 'state-decorator';
|
|
1732
|
+
```
|
|
1733
|
+
|
|
1734
|
+
### Step 2: Migrate code
|
|
1735
|
+
|
|
1736
|
+
#### Config
|
|
1737
|
+
|
|
1738
|
+
```diff
|
|
1739
|
+
- const getInitialState: (p:Props) => ({myProp: ''});
|
|
1740
|
+
-
|
|
1741
|
+
- const actions: StoreActions<State, Actions, Props> = {
|
|
1742
|
+
- myAction: ({s, args: [v]}) => ({ ...s, myProp: v })
|
|
1743
|
+
-}
|
|
1744
|
+
-
|
|
1745
|
+
- const options: StoreOptions<State, Action, Props> = {
|
|
1746
|
+
- onMount: () => {},
|
|
1747
|
+
- onPropsChange: {
|
|
1748
|
+
- getDeps: () => [],
|
|
1749
|
+
- effects: ({s, p}) => ({ ...s, myProp: p.prop }),
|
|
1750
|
+
- }
|
|
1751
|
+
- };
|
|
1752
|
+
-
|
|
1753
|
+
- function MyComponent(p:Props) {
|
|
1754
|
+
- const {state, actions} = useLocalStore(getInitialState, actions, props, options, [logEffects()]);
|
|
1755
|
+
- return <div>{myProp}</div>;
|
|
1756
|
+
- }
|
|
1757
|
+
|
|
1758
|
+
+ const config:StoreConfig<State, Actions, Props> = {
|
|
1759
|
+
+ getInitialState: () => ({ myProp: '' }),
|
|
1760
|
+
+ actions: {
|
|
1761
|
+
+ myAction: ({args: [v]}) => ({ myProp: v})
|
|
1762
|
+
+ // or
|
|
1763
|
+
+ myAction: setArgIn('myProp'),
|
|
1764
|
+
+ },
|
|
1765
|
+
+ onMount: () => {},
|
|
1766
|
+
+ onPropsChange: {
|
|
1767
|
+
+ getDeps: () => [],
|
|
1768
|
+
+ effects: ({ p }) => ({ myProp: p.prop })
|
|
1769
|
+
+ },
|
|
1770
|
+
+ middlewares: [logEffects()],
|
|
1771
|
+
+ }
|
|
1772
|
+
+
|
|
1773
|
+
+ function MyComponent(p:Props) {
|
|
1774
|
+
+ const {state, actions} = useLocalStore(config);
|
|
1775
|
+
+ return <div>{myProp}</div>;
|
|
1776
|
+
+ }
|
|
1777
|
+
```
|
|
1778
|
+
|
|
1779
|
+
## V5 to V6
|
|
1780
|
+
|
|
1781
|
+
### Step 1: v5 compatibility layer
|
|
1679
1782
|
|
|
1680
1783
|
To use the new engine while still using v5 API, juste import the v5 compatibility layer.
|
|
1681
1784
|
You can new store in new code.
|
|
@@ -1696,26 +1799,26 @@ setGlobalConfig({
|
|
|
1696
1799
|
});
|
|
1697
1800
|
```
|
|
1698
1801
|
|
|
1699
|
-
|
|
1802
|
+
### Step 2: Migrate code but keep v5 tests
|
|
1700
1803
|
|
|
1701
|
-
|
|
1804
|
+
#### Code
|
|
1702
1805
|
|
|
1703
|
-
|
|
1806
|
+
##### Types
|
|
1704
1807
|
|
|
1705
1808
|
| Before | After |
|
|
1706
1809
|
| --------------------- | ------------ |
|
|
1707
1810
|
| StateDecoratorActions | StoreActions |
|
|
1708
1811
|
| StateDecoratorOptions | StoreOptions |
|
|
1709
1812
|
|
|
1710
|
-
|
|
1813
|
+
##### Optimistic actions
|
|
1711
1814
|
|
|
1712
1815
|
If you are using optimistic effects, make sure to set the **optimisticActions** middleware to your stores, see [Optimistic actions](#optimistic-actions).
|
|
1713
1816
|
|
|
1714
|
-
|
|
1817
|
+
#### Hooks
|
|
1715
1818
|
|
|
1716
1819
|
Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
1717
1820
|
|
|
1718
|
-
|
|
1821
|
+
#### Actions
|
|
1719
1822
|
|
|
1720
1823
|
- rename action properties
|
|
1721
1824
|
|
|
@@ -1756,9 +1859,9 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1756
1859
|
};
|
|
1757
1860
|
```
|
|
1758
1861
|
|
|
1759
|
-
|
|
1862
|
+
#### Options
|
|
1760
1863
|
|
|
1761
|
-
|
|
1864
|
+
##### onMount
|
|
1762
1865
|
|
|
1763
1866
|
```diff
|
|
1764
1867
|
- const options: StateDecoratorOptions<S, A, P> = {
|
|
@@ -1770,7 +1873,7 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1770
1873
|
+} ;
|
|
1771
1874
|
```
|
|
1772
1875
|
|
|
1773
|
-
|
|
1876
|
+
##### logEnabled
|
|
1774
1877
|
|
|
1775
1878
|
```diff
|
|
1776
1879
|
- function Container(props: Props) {
|
|
@@ -1786,7 +1889,7 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1786
1889
|
+ }
|
|
1787
1890
|
```
|
|
1788
1891
|
|
|
1789
|
-
|
|
1892
|
+
##### Props changes
|
|
1790
1893
|
|
|
1791
1894
|
```diff
|
|
1792
1895
|
- const options: StateDecoratorOptions<S, A, P> = {
|
|
@@ -1808,7 +1911,7 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1808
1911
|
+} ;
|
|
1809
1912
|
```
|
|
1810
1913
|
|
|
1811
|
-
|
|
1914
|
+
#### Reuse v5 tests with v6 actions
|
|
1812
1915
|
|
|
1813
1916
|
```diff
|
|
1814
1917
|
- import { testSyncAction, testAsyncAction } from 'state-decorator';
|
|
@@ -1816,7 +1919,7 @@ Choose one of _useLocalStore_, _useStore_, _useBindStore_ or _useStoreSlice_.
|
|
|
1816
1919
|
+ import { testV6SyncAction, testV6AsyncAction } from 'state-decorator/v5_test';
|
|
1817
1920
|
```
|
|
1818
1921
|
|
|
1819
|
-
|
|
1922
|
+
### Step 3 (optional): Migrate tests
|
|
1820
1923
|
|
|
1821
1924
|
🎊 Congratulations! 🎊
|
|
1822
1925
|
|
|
@@ -1864,34 +1967,27 @@ You can add this snippet to quickly create a stateful functional component using
|
|
|
1864
1967
|
"import React from 'react';",
|
|
1865
1968
|
"import { useStore, StoreActions, LoadingProps } from 'state-decorator';",
|
|
1866
1969
|
"",
|
|
1867
|
-
"export type
|
|
1868
|
-
"",
|
|
1869
|
-
"export type $1State = {};",
|
|
1970
|
+
"export type Props = {};",
|
|
1870
1971
|
"",
|
|
1871
|
-
"export type
|
|
1972
|
+
"export type State = {};",
|
|
1872
1973
|
"",
|
|
1873
|
-
"type
|
|
1874
|
-
"type State = $1State;",
|
|
1875
|
-
"type Actions = $1Actions;",
|
|
1974
|
+
"export type Actions = {};",
|
|
1876
1975
|
"",
|
|
1877
1976
|
"type ViewProps = Props & Actions & State & Pick<LoadingProps<Actions>, 'loadingMap'>;",
|
|
1878
1977
|
"",
|
|
1879
|
-
"export
|
|
1880
|
-
"
|
|
1978
|
+
"export const storeConfig: StoreConfig<State, Actions, Props> = {",
|
|
1979
|
+
" getInitialState: (p:$1Props) => ({",
|
|
1980
|
+
" }),",
|
|
1981
|
+
" actions: {",
|
|
1982
|
+
" }",
|
|
1881
1983
|
"};",
|
|
1882
1984
|
"",
|
|
1883
1985
|
"export function $1View(p: ViewProps) {",
|
|
1884
1986
|
" return (<div>[$1View]</div>);",
|
|
1885
1987
|
"}",
|
|
1886
1988
|
"",
|
|
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
1989
|
"export default React.memo(function $1(p: $1Props) {",
|
|
1894
|
-
" const { state:s, actions:a, loadingMap } =
|
|
1990
|
+
" const { state:s, actions:a, loadingMap } = useLocalStore(config, p);",
|
|
1895
1991
|
" return <$1View {...p} {...s} {...a} loadingMap={loadingMap} />;",
|
|
1896
1992
|
"});"
|
|
1897
1993
|
],
|