state-decorator 6.12.2 → 7.0.0-beta.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 CHANGED
@@ -11,12 +11,22 @@ The StateDecorator is a set of Reacts hook that manages a complex component stat
11
11
  - Deterministic state changes (clear separation of effects and side effects)
12
12
  - Ease asynchronous actions state changes (loading / error states, success & error handlers, parallel actions management, optimistic updates...)
13
13
  - Easily and efficiently share slices of state.
14
- - Easily testable (uses pure functions, utility functions provided)
14
+ - Easily testable (test framework provided)
15
15
  - Easily update state from or react to props changes
16
- - Ease debugging (trace state changes)
16
+ - Ease debugging (trace state changes, dev tools)
17
17
  - Improve code conciseness (no boiler plate code)
18
- - Enforce separation of container components and presentation components.
19
- - Strongly typed
18
+ - Strongly typed (Typescript)
19
+
20
+ # V7: Partial state and new hooks
21
+
22
+ - React 17 (upcoming V8 for React 18)
23
+ - All effects can now return a partial state that will be merged with current state.
24
+ - More documentation on global and local store use cases.
25
+ - **useLocalStore** and **useStore** now have a parameter to specify if the React component must re-rerender after a state change.
26
+ - New **useStoreContextSlice** hook was added to get a slice of a store in a context.
27
+ - New **onMountDeferred** to execute initialization code after initial render
28
+ - New effect helpers
29
+ - Removed useBindStore hook.
20
30
 
21
31
  # 🏎️ V6: The Store
22
32
 
@@ -82,13 +92,12 @@ export const getInitialState = (): State => ({
82
92
 
83
93
  export const userAppActions: StoreActions<State, Actions, Props> = {
84
94
  // synchronous action (simple form)
85
- selectUser: ({ state, args: [id] }) => ({ ...state, selectedUserId: id }),
95
+ selectUser: ({ args: [id] }) => ({ selectedUserId: id }),
86
96
 
87
97
  // a synchronous action with side effects
88
98
  selectAndLoadUser: {
89
- // same as simple form, using aliases (use short context alias 's' instead of 'state')
90
99
  // effects contain only immutable state changes
91
- effects: ({ s, args: [id] }) => ({ ...s, selectedUserId: id }),
100
+ effects: ({ args: [id] }) => ({ selectedUserId: id }),
92
101
  // side effects (ie. other effects than state changes), using "actions" alias
93
102
  sideEffects: ({ a }) => {
94
103
  a.loadUser();
@@ -100,9 +109,9 @@ export const userAppActions: StoreActions<State, Actions, Props> = {
100
109
  // promise provider, can use action context (ctx) to get current state, args, props etc.
101
110
  getPromise: (ctx) => Promise.resolve({ firstName: 'John', lastName: 'Doe' }),
102
111
  // state changes
103
- effects: ({ s, res }) => ({ ...s, user: res }),
112
+ effects: ({ res }) => ({ user: res }),
104
113
  // state changes if error
105
- errorEffects: ({ s, error }) => ({ ...s, user: null }),
114
+ errorEffects: ({ error }) => ({ user: null }),
106
115
  // side effects here
107
116
  sideEffects: ({ res, actions }) => {},
108
117
  // side effects if error
@@ -139,12 +148,12 @@ export function App(props: Props) => {
139
148
 
140
149
  # React hooks
141
150
 
142
- | Hook | Purpose | Component refreshed on store change | Store is destroyed on unmount |
143
- | ------------- | ------------------------------------------------------ | ----------------------------------- | ----------------------------- |
144
- | useLocalStore | Create a store and binds it to the react component | Y | Y |
145
- | useStore | Binds an existing store to a react component (sharing) | Y | Y |
146
- | useBindStore | Bind an existing store to a react component (sharing) | N | Y |
147
- | useStoreSlice | Binds a store slice to a react component (sharing) | If slice has changed only | N |
151
+ | Hook | Purpose | Returns | Is component refreshed on store change? | Is store destroyed on unmount? |
152
+ | -------------------- | --------------------------------------------------- | ------- | --------------------------------------- | ------------------------------ |
153
+ | useLocalStore | Create a store and binds it to the react component. | Store | if **refreshOnUpdate** is set | Y |
154
+ | useStore | Binds a store instance to the react component. | Store | if **refreshOnUpdate** is set | N |
155
+ | useStoreSlice | Get a slice of a store. | Slice | If slice has changed only | N |
156
+ | useStoreContextSlice | Get a slice of a store stored in a context | Slice | If slice has changed only | N |
148
157
 
149
158
  # Initial state
150
159
 
@@ -190,14 +199,14 @@ The StateDecorator is taking a list of actions and decorate them to inject state
190
199
  ## Short form
191
200
 
192
201
  ```typescript
193
- action: (ctx) => ctx.state, // state changes
202
+ action: (ctx) => ctx.state, // changes to be merged to current state
194
203
  ```
195
204
 
196
205
  Example:
197
206
 
198
207
  ```typescript
199
208
  const actions: StoreActions<State, Actions> = {
200
- setText: ({ s, args: [newText] }) => ({ ...s, text: newText }),
209
+ setText: ({ args: [newText] }) => ({ text: newText }),
201
210
  };
202
211
  ```
203
212
 
@@ -222,7 +231,7 @@ Example:
222
231
  ```typescript
223
232
  const actions: StoreActions<State, Actions> = {
224
233
  setText: {
225
- effects: ({ s, args: [newText] }) => ({ ...s, text: newText }),
234
+ effects: ({ args: [newText] }) => ({ text: newText }),
226
235
  // use either debounceTimeout or debounceSideEffectsTimeout
227
236
  // debounceTimeout: 5000,
228
237
  debounceSideEffectsTimeout: 5000,
@@ -272,7 +281,7 @@ Example:
272
281
  const actions: StoreActions<State, Actions> = {
273
282
  loadList: {
274
283
  getPromise: () => fetch('https://myapi/myservices').then((res) => (res.ok ? res.json() : Promise.reject())),
275
- effects: ({ s, res }) => ({ ...s, list: res }),
284
+ effects: ({ res }) => ({ list: res }),
276
285
  sideEffects: ({ a }) => {
277
286
  a.otherAction();
278
287
  },
@@ -395,7 +404,7 @@ If some actions need to be triggered, set **errorSideEffects**.
395
404
  const actions: StoreActions<State, Actions> = {
396
405
  loadList: {
397
406
  getPromise: () => new Promise((_, reject) => setTimeout(reject, 500, new Error('Too bad'))),
398
- errorEffects: ({ state, error }) => ({ ...state, error: error.message, list: [] }),
407
+ errorEffects: ({ error }) => ({ error: error.message, list: [] }),
399
408
  getErrorMessage: ({ err }) => err.message,
400
409
  errorSideEffects: ({ error }) => {
401
410
  Logger.log(error);
@@ -517,8 +526,8 @@ Example:
517
526
  const actions: StoreActions<State, Actions> = {
518
527
  deleteItem: {
519
528
  getPromise: ([id]) => new Promise((resolve) => setTimeout(resolve, 500)),
520
- optimisticEffects: (state, args) => ({ ...state, list: list.filter((item) => item.id === args[0]) }),
521
- errorEffects: (state, error: Error) => ({ ...state, error: error.message }),
529
+ optimisticEffects: ({ args: [id] }) => ({ list: list.filter((item) => item.id === id) }),
530
+ errorEffects: ({err} => ({ err: error.message }),
522
531
  },
523
532
  };
524
533
  ```
@@ -554,9 +563,9 @@ export const actionsAbort: StoreActions<State, Actions, Props> = {
554
563
  reject(new DOMException('Aborted', 'AbortError'));
555
564
  });
556
565
  }),
557
- errorEffects: ({s, err}) => (err.name === 'AbortError' ? { ...s, isAborted: true } : { ...s, isError: true }),
558
- effects: ({s}) => ({ ...s, isSuccess: true }),
559
- errorSideEffects: ({s, err}) => {
566
+ errorEffects: ({ err}) => (err.name === 'AbortError' ? { isAborted: true } : { isError: true }),
567
+ effects: () => ({ isSuccess: true }),
568
+ errorSideEffects: ({ err }) => {
560
569
  if (e.name === 'AbortError') {
561
570
  console.log('AbortError side effect');
562
571
  } else {
@@ -571,6 +580,40 @@ Note: ongoing abortable actions will be automatically aborted on store destructi
571
580
 
572
581
  [![Edit Abort](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/abort-v6-uvnl7)
573
582
 
583
+ ## Helper effect functions
584
+
585
+ For common / simple use cases, some helpers functions are provided to write basic action effects and improve actions readability.
586
+
587
+ Example
588
+
589
+ ```typescript
590
+ import { setArgIn } from 'state-decorator/helpers';
591
+
592
+ type State = {
593
+ myProp: string;
594
+ };
595
+
596
+ type Actions = {
597
+ setMyProp: (v: string) => void;
598
+ };
599
+
600
+ const actions: StoreActions<State, Actions> = {
601
+ setMyProp: setArgIn('myProp'),
602
+ // same as
603
+ // setMyProp: ({ args: [v] }) => ({ myProp: v }),
604
+ };
605
+ ```
606
+
607
+ | Name | Purpose | Action signature |
608
+ | ------------ | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
609
+ | setArgIn | Effect function to update a state property from action first argument | (v:T) => void |
610
+ | setTrueIn | Effect function to update a boolean state property to true | () => void |
611
+ | setFalseIn | Effect function to update a boolean state property to false | () => void |
612
+ | toggleProp | Effect function to toggle a boolean state property | () => void |
613
+ | setMapProp | 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 |
614
+ | setArrayItem | 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 |
615
+ | setResIn | Effect function of asynchronous action to update a state property with the result of the promise | () => Promise<T> |
616
+
574
617
  ## Call actions on mount
575
618
 
576
619
  When store is created (for example whe React component in mounted), the **onMount** option is called.
@@ -580,7 +623,21 @@ import { createStore } from 'state-decorator';
580
623
 
581
624
  const store = createStore(getInitialState, actionsImpl, {
582
625
  onMount: ({ a }) => {
583
- a.loadList;
626
+ a.loadList();
627
+ },
628
+ });
629
+ ```
630
+
631
+ After React component is mounted using _useLocalStore_, the **onMountDeferred** option is called (in a _useEffect_).
632
+ The **onMount** is called _during_ the render of the hooks. If the onMount code is containing a function that is changing parent state,
633
+ React will not update the parent state and will issue a warning.
634
+
635
+ ```typescript
636
+ import { createStore } from 'state-decorator';
637
+
638
+ const store = createStore(getInitialState, actionsImpl, {
639
+ onMountDeferred: ({ p }) => {
640
+ p.loadParentList();
584
641
  },
585
642
  });
586
643
  ```
@@ -646,34 +703,6 @@ export function SubComponent() {
646
703
 
647
704
  [![Edit Slice](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/slices-v6-eg471?file=/src/SliceView.tsx)
648
705
 
649
- # Global configuration
650
-
651
- Overrides configuration to set properties that will be used by all stores.
652
-
653
- ```typescript
654
- // all parameters are optional
655
- setGlobalConfig({
656
- // Used to clone state and props when managing optimistic conflicting actions.
657
- clone: defaultCloneFunc,
658
- // Compare to objets and returns if they are equal (slices).
659
- comparator: shallow,
660
- // Callback function to handle asychronous actions rejected promise (error reporting).
661
- asyncErrorHandler: () => {},
662
- // Tests if the error will trigger a retry of the action or will fail directly (retry promises)
663
- retryOnErrorFunction: (error: Error) => error instanceof TypeError,
664
- // Notification function on successful asynchronous action (if success message is set on action)
665
- notifySuccess: undefined,
666
- // Notification function on failed asynchronous action (if error message is set on action)
667
- notifyError: undefined,
668
- // Notification function injected in side effects action context (success and error) to notify warning
669
- notifyWarning: undefined,
670
- // Function called to return common error message if error message is not provided or error not managed in action.
671
- // To override locally in action, provide a function that returns _null_.
672
- // If _isErrorManaged_ is set to _true_, this function will not be called.
673
- getErrorMessage: undefined,
674
- });
675
- ```
676
-
677
706
  # Update store when props change
678
707
 
679
708
  ## Getting started
@@ -686,7 +715,7 @@ const store = createStore(getInitialState, actionsImpl, {
686
715
  // list of depedencies that should trigger effects & side effects if changed
687
716
  getDeps: (p) => [p.id],
688
717
  // state changes, indices contains the indices of dependencies that have changed
689
- effects: ({ state, props, indices, isInit }) => ({ ...state, selectedId: props.id }),
718
+ effects: ({ props, indices, isInit }) => ({ selectedId: props.id }),
690
719
  // other changes (use short aliases)
691
720
  sideEffects: ({ p, a, indices, isInit }) => {
692
721
  a.loadItem(p.id);
@@ -722,7 +751,7 @@ const store = createStore(getInitialState, actionsImpl, {
722
751
  },
723
752
  {
724
753
  getDeps: (p) => [p.otherProp],
725
- effects: ({ state, props, indices }) => ({ ...state, otherProp: props.otherProp }),
754
+ effects: ({ props, indices }) => ({ otherProp: props.otherProp }),
726
755
  sideEffects: ({ p, a }) => {
727
756
  a.otherSideEffect(p.otherProp);
728
757
  },
@@ -745,7 +774,7 @@ const store = createStore(getInitialState, actionsImpl, {
745
774
  onPropsChange: [
746
775
  {
747
776
  getDeps: (p) => [p.userId],
748
- effects: ({ s, p }) => ({ ...s, userId: p.userId }),
777
+ effects: ({ p }) => ({ userId: p.userId }),
749
778
  sideEffects: ({ p, a }) => {
750
779
  a.loadUser(p.userId);
751
780
  },
@@ -759,7 +788,7 @@ const store = createStore(getInitialState, actionsImpl, {
759
788
  onPropsChange: [
760
789
  {
761
790
  getDeps: (p) => [p.userId],
762
- effects: ({ s, p, isInit }) => (isInit ? null : { ...s, userId: p.userId }),
791
+ effects: ({ p, isInit }) => (isInit ? null : { userId: p.userId }),
763
792
  sideEffects: ({ p, a }) => {
764
793
  a.loadUser(p.userId);
765
794
  },
@@ -776,6 +805,210 @@ const store = createStore(getInitialState, actionsImpl, {
776
805
  - Use several prop change configurations to separate dependencies between props and have simple definition.
777
806
  - Use onMount flag to have a more systematic and simpler code.
778
807
 
808
+ # Global / local Stores and State sharing
809
+
810
+ There are two ways to share state and actions:
811
+
812
+ - Using regular props.
813
+ - Using state slices.
814
+
815
+ There are two types of stores:
816
+
817
+ - Global store:
818
+
819
+ - store is created as an instance (**createStore**) and it is exported from the JS module.
820
+ - it can be imported in any other JS module.
821
+ - store lifespan is equal to the application one.
822
+
823
+ - Local store:
824
+ - store is created by a hook (**useLocalStore**) and is bound to a React component (each instance of this component owns its own store).
825
+ - action and props can be shared using props or sharing the store itself (see example below)
826
+ - store is destroyed when its owner React component is unmounted.
827
+
828
+ ## Using props
829
+
830
+ - Bind a React component to make store state / actions available on React component.
831
+ - Any change in the store (state, loading actions, ...) will trigger a refresh of the bound React component.
832
+ - Pass state / actions using regular React props.
833
+ - Use React.memo to prevent unecessary React re-renders if needed.
834
+
835
+ ### Global store
836
+
837
+ ```typescript
838
+ // Declare typings & actions as above
839
+
840
+ // Create a store in a dedicated file, named GlobalStore.ts for example
841
+ export const store = createStore(getInitialState, userAppActions);
842
+
843
+ //----------------------
844
+
845
+ // In another file
846
+ import React, { memo } from 'react';
847
+ import { useStore } from 'state-decorator';
848
+ import store from './GlobalStore';
849
+
850
+ // Bind to react component
851
+ // Each time the store state changes, a re-render is done.
852
+ export function Container(props: Props) {
853
+ const { state, actions } = useStore(store, props);
854
+ return (
855
+ <div>
856
+ <Title text={state.title} />
857
+ <Subtitle text={state.subtitle} />
858
+ </div>
859
+ );
860
+ }
861
+
862
+ // Simple presentation components
863
+ // Use memo to prevent re-render if another part of the state has changed than "title"
864
+ const Title = memo(function Title(props: { title: string }) {
865
+ return <div>{props.text}</div>;
866
+ });
867
+
868
+ const Subtitle = memo(function Subtitle(props: { subtitle: string }) {
869
+ return <div>{props.subtitle}</div>;
870
+ });
871
+ ```
872
+
873
+ ### Local Store
874
+
875
+ ```typescript
876
+ import React, { memo } from 'react';
877
+ import { useLocalStore } from 'state-decorator';
878
+
879
+ // Declare typings & actions as above
880
+
881
+ // Create and bind to react component
882
+ // Each time the store state changes, a re-render is done.
883
+ export function Container(props: Props) {
884
+ const { state, actions } = useLocalStore(getInitialState, userAppActions, props);
885
+ return (
886
+ <div>
887
+ <Title text={state.title} />
888
+ <Subtitle text={state.subtitle} />
889
+ </div>
890
+ );
891
+ }
892
+
893
+ // Simple presentation components
894
+ // Use memo to prevent re-render if another part of the state has changed than "title"
895
+ const Title = memo(function Title(props: { title: string }) {
896
+ return <div>{props.title}</div>;
897
+ });
898
+
899
+ const Subtitle = memo(function Subtitle(props: { subtitle: string }) {
900
+ return <div>{props.subtitle}</div>;
901
+ });
902
+ ```
903
+
904
+ ## State slices
905
+
906
+ Using React props implies useless re-renders and optionally memoization.
907
+
908
+ To prevent this, React provides [context](https://en.reactjs.org/docs/context.html) hooks.
909
+
910
+ The problem is that if the context contains a complex state with lots of props, if **any** of these props is changing, **all** components that uses this context will be re-rendered.
911
+
912
+ To overcome this problem, the StateDecorator provides a **useStoreSlice** and **useStoreContextSlice** hook that allow to define and extracts a slice of the state and trigger a re-render of the React component only if the slice has changed.
913
+
914
+ [![Edit Slice](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/slices-v6-eg471?file=/src/SliceView.tsx)
915
+
916
+ ## Global Store
917
+
918
+ ```typescript
919
+ // Declare typings & actions as above
920
+
921
+ // Create a store in a dedicated file, named GlobalStore.ts for example
922
+ export const store = createStore(getInitialState, userAppActions);
923
+
924
+ //----------------------
925
+
926
+ import React, { memo } from 'react';
927
+ import { useStoreSlice } from 'state-decorator';
928
+ import store from './GlobalStore';
929
+
930
+ export function Container(props: Props) {
931
+ return (
932
+ <div>
933
+ <Title />
934
+ <Subtitle />
935
+ </div>
936
+ );
937
+ }
938
+
939
+ function Title() {
940
+ // extracts the "title" slice
941
+ // component is refresh only if title is changed in the store
942
+ const { title } = useStoreSlice(store, ['title']);
943
+ return <div>{title}</div>;
944
+ }
945
+
946
+ function Subtitle(props: { subtitle: string }) {
947
+ const { subtitle } = useStoreSlice(store, ['subtitle']);
948
+ return <div>{subtitle}</div>;
949
+ }
950
+ ```
951
+
952
+ ## Local Store
953
+
954
+ ```typescript
955
+ // ------- stores/MyStore.ts --------
956
+
957
+ import React from 'react';
958
+ import { useLocalStore, useStoreContextSlice } from 'state-decorator';
959
+
960
+ // Declare typings & actions as above
961
+
962
+ // Create a store context
963
+
964
+ type StoreContextProps = StoreApi<State, Actions, Props>;
965
+
966
+ export const StoreContext = createContext<StoreContextProps>(null);
967
+
968
+ // The StoreContext will allow to access to the store
969
+ // But this context is NOT refreshed if the store state is changed
970
+ export function StoreContextProvider(p: { children: any; propIn: string }) {
971
+ const { children, ...props } = p;
972
+ const store = useLocalStore(getInitialState, actionsImpl, props);
973
+ return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
974
+ }
975
+
976
+ // In a container file ------------------------
977
+
978
+ import { StoreContextProvider } from 'stores/MyStore';
979
+
980
+ // If Container is destroyed store is destroyed
981
+ export function Container(props: Props) {
982
+ return (
983
+ <StoreContextProvider {...props}>
984
+ <SubComponent />
985
+ <SubComponent2 />
986
+ </StoreContextProvider>
987
+ );
988
+ }
989
+
990
+ // Component deeper in the component tree...
991
+
992
+ import { StoreContext } from 'stores/MyStore';
993
+
994
+ function Title() {
995
+ // extracts the "title" slice
996
+ // component is refresh only if title is changed in the store
997
+ const { title } = useStoreContextSlice(StoreContext, ['title']);
998
+
999
+ // same as
1000
+ // const store = useContext(StoreContext)
1001
+ // const { title } = useStoreSlice(store, ['title']);
1002
+
1003
+ return <div>{title}</div>;
1004
+ }
1005
+
1006
+ function Subtitle(props: { subtitle: string }) {
1007
+ const { subtitle } = useStoreContextSlice(StoreContext, ['subtitle']);
1008
+ return <div>{subtitle}</div>;
1009
+ }
1010
+ ```
1011
+
779
1012
  # Derived state
780
1013
 
781
1014
  A derived state is a state that can be deduced from state and props.
@@ -831,6 +1064,32 @@ export function App(props: Props) {
831
1064
  - In general, do **not** compute derived values directly in state, it's error prone as you can forget some places or implement different logic.
832
1065
  - Use derived state especially if this state is shared accross several sub components
833
1066
 
1067
+ # Global configuration
1068
+
1069
+ Overrides configuration to set properties that will be used by all stores.
1070
+
1071
+ ```typescript
1072
+ // all parameters are optional
1073
+ setGlobalConfig({
1074
+ // Used to clone state and props when managing optimistic conflicting actions.
1075
+ clone: defaultCloneFunc,
1076
+ // Compare to objets and returns if they are equal (slices).
1077
+ comparator: shallow,
1078
+ // Callback function to handle asychronous actions rejected promise (error reporting).
1079
+ asyncErrorHandler: () => {},
1080
+ // Tests if the error will trigger a retry of the action or will fail directly (retry promises)
1081
+ retryOnErrorFunction: (error: Error) => error instanceof TypeError,
1082
+ // Notification function on successful asynchronous action (if success message is set on action)
1083
+ notifySuccess: undefined,
1084
+ // Notification function on failed asynchronous action (if error message is set on action)
1085
+ notifyError: undefined,
1086
+ // Notification function injected in side effects action context (success and error) to notify warning
1087
+ notifyWarning: undefined,
1088
+ // Function called to return common error message if error message is not provided or error not managed in action. To override locally in action, provide a function that returns _null_.
1089
+ getErrorMessage: undefined,
1090
+ });
1091
+ ```
1092
+
834
1093
  # Debug actions
835
1094
 
836
1095
  To debug actions, add a middleware to the store.
@@ -862,7 +1121,7 @@ Example:
862
1121
 
863
1122
  ```
864
1123
  [StateDecorator sample] loadList effects {list: Array(10), prop1: "", prop2: "", prop3: ""} loading: false
865
- middlewares.ts:36 [StateDecorator sample] setProp1 effects {list: Array(10), prop1: "92", prop2: "", prop3: ""}
1124
+ [StateDecorator sample] setProp1 effects {list: Array(10), prop1: "92", prop2: "", prop3: ""}
866
1125
  ```
867
1126
 
868
1127
  ## Detailed Logger
@@ -914,11 +1173,11 @@ function Container(props: Props) {
914
1173
  - a new store is created each time state or props are set.
915
1174
  - it allows to share mock store across tests.
916
1175
  - The API is chainable: functions are returning a mock store, excepting test functions.
917
- - On a mock store, we can test the store internals after initialiation, after inbound props have changed, after actions are called.
1176
+ - On a mock store, we can test the store internals after initialiation, after inbound props have changed, after actions are called. These test function do not change the internal state of the mock store.
918
1177
 
919
1178
  ## Getting started
920
1179
 
921
- 1. Create and setup a mock store (_createMockFromStore_)
1180
+ 1. Create and setup a mock store (_createMockFromStore_, _createMockStore_)
922
1181
  2. Test store after initialization (_onInit_)
923
1182
  3. Test store after props changes (_onPropsChange_)
924
1183
  4. Test each action:
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Effect function that sets the first action argument to the specified prop.
3
+ */
4
+ export declare function setArgIn<S, K extends keyof S>(propName: K): (ctx: {
5
+ s: S;
6
+ args: [S[K]];
7
+ }) => Partial<S>;
8
+ /**
9
+ * Effect function that sets <code>true</code> in the specified prop.
10
+ */
11
+ export declare function setTrueIn<S, K extends keyof S>(propName: K): (ctx: {
12
+ s: S;
13
+ }) => Partial<S>;
14
+ /**
15
+ * Effect function that sets <code>false</code> in the specified prop.
16
+ */
17
+ export declare function setFalseIn<S, K extends keyof S>(propName: K): (ctx: {
18
+ s: S;
19
+ }) => Partial<S>;
20
+ /**
21
+ * Effect function that sets toggles the boolean value of the specified prop.
22
+ */
23
+ export declare function toggleProp<S, K extends keyof S>(propName: K): (ctx: {
24
+ s: S;
25
+ }) => Partial<S>;
26
+ /**
27
+ * Effect function that sets the update the specifed prop as an array using first action argument as index
28
+ * and second as value to set.
29
+ */
30
+ export declare function setMapItem<S, K extends keyof S, T>(propName: K): (ctx: {
31
+ s: S;
32
+ args: [string, T];
33
+ }) => Partial<S>;
34
+ /**
35
+ * Effect function that sets the update the specifed prop as an array using first action argument as index
36
+ * and second as value to set.
37
+ */
38
+ export declare function setArrayItem<S, K extends keyof S, T>(propName: K): (ctx: {
39
+ s: S;
40
+ args: [number, T];
41
+ }) => Partial<S>;
42
+ /**
43
+ * Effect function that sets the asynchronous action result to the specified prop.
44
+ */
45
+ export declare function setResIn<S, K extends keyof S>(propName: K): (ctx: {
46
+ s: S;
47
+ res: S[K];
48
+ }) => Partial<S>;
package/es/helpers.js ADDED
@@ -0,0 +1,2 @@
1
+ var n=function(){return(n=Object.assign||function(n){for(var r,t=1,u=arguments.length;t<u;t++)for(var e in r=arguments[t])Object.prototype.hasOwnProperty.call(r,e)&&(n[e]=r[e]);return n}).apply(this,arguments)};function r(n){return function(r){var t,u=r.s,e=r.args[0];return u[n]===e?null:((t={})[n]=e,t)}}function t(n){return function(r){var t;return r.s[n]?null:((t={})[n]=!0,t)}}function u(n){return function(r){var t;return r.s[n]?((t={})[n]=!1,t):null}}function e(n){return function(r){var t,u=r.s;return(t={})[n]=!u[n],t}}function o(r){return function(t){var u,e,o=t.s,c=t.args,i=c[0],a=c[1];return(u={})[r]=n(n({},o[r]),((e={})[i]=a,e)),u}}function c(n){return function(r){var t,u=r.s,e=r.args,o=e[0],c=e[1],i=function(n,r,t){if(t||2===arguments.length)for(var u,e=0,o=r.length;e<o;e++)!u&&e in r||(u||(u=Array.prototype.slice.call(r,0,e)),u[e]=r[e]);return n.concat(u||Array.prototype.slice.call(r))}([],u[n],!0);return i[o]=c,(t={})[n]=i,t}}function i(n){return function(r){var t,u=r.res;return(t={})[n]=u,t}}export{r as setArgIn,c as setArrayItem,u as setFalseIn,o as setMapItem,i as setResIn,t as setTrueIn,e as toggleProp};
2
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sources":["../../node_modules/tslib/tslib.es6.js"],"sourcesContent":["/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n"],"names":["__assign","Object","assign","t","s","i","n","arguments","length","p","prototype","hasOwnProperty","call","apply","this","to","from","pack","ar","l","Array","slice","concat"],"mappings":"AA+BO,IAAIA,EAAW,WAQlB,OAPAA,EAAWC,OAAOC,QAAU,SAAkBC,GAC1C,IAAK,IAAIC,EAAGC,EAAI,EAAGC,EAAIC,UAAUC,OAAQH,EAAIC,EAAGD,IAE5C,IAAK,IAAII,KADTL,EAAIG,UAAUF,GACOJ,OAAOS,UAAUC,eAAeC,KAAKR,EAAGK,KAAIN,EAAEM,GAAKL,EAAEK,IAE9E,OAAON,IAEKU,MAAMC,KAAMP,sgBA6HzB,SAAuBQ,EAAIC,EAAMC,GACpC,GAAIA,GAA6B,IAArBV,UAAUC,OAAc,IAAK,IAA4BU,EAAxBb,EAAI,EAAGc,EAAIH,EAAKR,OAAYH,EAAIc,EAAGd,KACxEa,GAAQb,KAAKW,IACRE,IAAIA,EAAKE,MAAMV,UAAUW,MAAMT,KAAKI,EAAM,EAAGX,IAClDa,EAAGb,GAAKW,EAAKX,IAGrB,OAAOU,EAAGO,OAAOJ,GAAME,MAAMV,UAAUW,MAAMT,KAAKI"}
package/es/impl.d.ts CHANGED
@@ -188,7 +188,7 @@ export declare function areSameArgs(args1: any[], args2: any[]): boolean;
188
188
  /** @internal */
189
189
  export declare function computeDerivedValues<S, A, P, DS>(s: Ref<S>, p: Ref<P>, derivedStateRef: Ref<DerivedState<DS>>, options: StoreOptions<S, A, P, DS>): boolean;
190
190
  /** @internal */
191
- export declare function onPropChange<S, P, A, DS>(stateRef: Ref<S>, derivedStateRef: Ref<DerivedState<DS>>, propsRef: Ref<P>, oldProps: P, actionsRef: Ref<A>, options: StoreOptions<S, A, P, DS>, setState: SetStateFunc<S, A>, isInit: boolean): void;
191
+ export declare function onPropChange<S, P, A, DS>(stateRef: Ref<S>, derivedStateRef: Ref<DerivedState<DS>>, propsRef: Ref<P>, oldProps: P, actionsRef: Ref<A>, options: StoreOptions<S, A, P, DS>, setState: SetStateFunc<S, A>, isInit: boolean, isDeferred: boolean): void;
192
192
  /** @internal */
193
193
  export declare function runMiddlewares<S, A extends DecoratedActions, P>(middlewares: Middleware<S, A, P>[], oldState: S, newState: S, loading: boolean, actionName: keyof A, actionType: 'preEffects' | 'effects' | 'errorEffects' | null, isAsync: boolean, actionCtx: any): {
194
194
  newState: S;