state-decorator 6.10.0 → 7.0.0-beta.0

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,19 @@ 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
+ - All effects can now return a partial state that will be merged with current state.
23
+ - More documentation on global and local store use cases.
24
+ - **useLocalStore** and **useStore** now have a parameter to specify if the React component must re-rerender after a state change.
25
+ - New **useStoreContextSlice** hook was added to get a slice of a store in a context.
26
+ - Removed useBindStore hook.
20
27
 
21
28
  # 🏎️ V6: The Store
22
29
 
@@ -82,13 +89,12 @@ export const getInitialState = (): State => ({
82
89
 
83
90
  export const userAppActions: StoreActions<State, Actions, Props> = {
84
91
  // synchronous action (simple form)
85
- selectUser: ({ state, args: [id] }) => ({ ...state, selectedUserId: id }),
92
+ selectUser: ({ args: [id] }) => ({ selectedUserId: id }),
86
93
 
87
94
  // a synchronous action with side effects
88
95
  selectAndLoadUser: {
89
- // same as simple form, using aliases (use short context alias 's' instead of 'state')
90
96
  // effects contain only immutable state changes
91
- effects: ({ s, args: [id] }) => ({ ...s, selectedUserId: id }),
97
+ effects: ({ args: [id] }) => ({ selectedUserId: id }),
92
98
  // side effects (ie. other effects than state changes), using "actions" alias
93
99
  sideEffects: ({ a }) => {
94
100
  a.loadUser();
@@ -100,9 +106,9 @@ export const userAppActions: StoreActions<State, Actions, Props> = {
100
106
  // promise provider, can use action context (ctx) to get current state, args, props etc.
101
107
  getPromise: (ctx) => Promise.resolve({ firstName: 'John', lastName: 'Doe' }),
102
108
  // state changes
103
- effects: ({ s, res }) => ({ ...s, user: res }),
109
+ effects: ({ res }) => ({ user: res }),
104
110
  // state changes if error
105
- errorEffects: ({ s, error }) => ({ ...s, user: null }),
111
+ errorEffects: ({ error }) => ({ user: null }),
106
112
  // side effects here
107
113
  sideEffects: ({ res, actions }) => {},
108
114
  // side effects if error
@@ -139,12 +145,12 @@ export function App(props: Props) => {
139
145
 
140
146
  # React hooks
141
147
 
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 |
148
+ | Hook | Purpose | Returns | Is component refreshed on store change? | Is store destroyed on unmount? |
149
+ | -------------------- | --------------------------------------------------- | ------- | --------------------------------------- | ------------------------------ |
150
+ | useLocalStore | Create a store and binds it to the react component. | Store | if **refreshOnUpdate** is set | Y |
151
+ | useStore | Binds a store instance to the react component. | Store | if **refreshOnUpdate** is set | N |
152
+ | useStoreSlice | Get a slice of a store. | Slice | If slice has changed only | N |
153
+ | useStoreContextSlice | Get a slice of a store stored in a context | Slice | If slice has changed only | N |
148
154
 
149
155
  # Initial state
150
156
 
@@ -190,14 +196,14 @@ The StateDecorator is taking a list of actions and decorate them to inject state
190
196
  ## Short form
191
197
 
192
198
  ```typescript
193
- action: (ctx) => ctx.state, // state changes
199
+ action: (ctx) => ctx.state, // changes to be merged to current state
194
200
  ```
195
201
 
196
202
  Example:
197
203
 
198
204
  ```typescript
199
205
  const actions: StoreActions<State, Actions> = {
200
- setText: ({ s, args: [newText] }) => ({ ...s, text: newText }),
206
+ setText: ({ args: [newText] }) => ({ text: newText }),
201
207
  };
202
208
  ```
203
209
 
@@ -222,7 +228,7 @@ Example:
222
228
  ```typescript
223
229
  const actions: StoreActions<State, Actions> = {
224
230
  setText: {
225
- effects: ({ s, args: [newText] }) => ({ ...s, text: newText }),
231
+ effects: ({ args: [newText] }) => ({ text: newText }),
226
232
  // use either debounceTimeout or debounceSideEffectsTimeout
227
233
  // debounceTimeout: 5000,
228
234
  debounceSideEffectsTimeout: 5000,
@@ -272,7 +278,7 @@ Example:
272
278
  const actions: StoreActions<State, Actions> = {
273
279
  loadList: {
274
280
  getPromise: () => fetch('https://myapi/myservices').then((res) => (res.ok ? res.json() : Promise.reject())),
275
- effects: ({ s, res }) => ({ ...s, list: res }),
281
+ effects: ({ res }) => ({ list: res }),
276
282
  sideEffects: ({ a }) => {
277
283
  a.otherAction();
278
284
  },
@@ -395,7 +401,7 @@ If some actions need to be triggered, set **errorSideEffects**.
395
401
  const actions: StoreActions<State, Actions> = {
396
402
  loadList: {
397
403
  getPromise: () => new Promise((_, reject) => setTimeout(reject, 500, new Error('Too bad'))),
398
- errorEffects: ({ state, error }) => ({ ...state, error: error.message, list: [] }),
404
+ errorEffects: ({ error }) => ({ error: error.message, list: [] }),
399
405
  getErrorMessage: ({ err }) => err.message,
400
406
  errorSideEffects: ({ error }) => {
401
407
  Logger.log(error);
@@ -517,8 +523,8 @@ Example:
517
523
  const actions: StoreActions<State, Actions> = {
518
524
  deleteItem: {
519
525
  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 }),
526
+ optimisticEffects: ({ args: [id] }) => ({ list: list.filter((item) => item.id === id) }),
527
+ errorEffects: ({err} => ({ err: error.message }),
522
528
  },
523
529
  };
524
530
  ```
@@ -554,9 +560,9 @@ export const actionsAbort: StoreActions<State, Actions, Props> = {
554
560
  reject(new DOMException('Aborted', 'AbortError'));
555
561
  });
556
562
  }),
557
- errorEffects: ({s, err}) => (err.name === 'AbortError' ? { ...s, isAborted: true } : { ...s, isError: true }),
558
- effects: ({s}) => ({ ...s, isSuccess: true }),
559
- errorSideEffects: ({s, err}) => {
563
+ errorEffects: ({ err}) => (err.name === 'AbortError' ? { isAborted: true } : { isError: true }),
564
+ effects: () => ({ isSuccess: true }),
565
+ errorSideEffects: ({ err }) => {
560
566
  if (e.name === 'AbortError') {
561
567
  console.log('AbortError side effect');
562
568
  } else {
@@ -569,96 +575,92 @@ export const actionsAbort: StoreActions<State, Actions, Props> = {
569
575
 
570
576
  [![Edit Abort](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/abort-v6-uvnl7)
571
577
 
572
- ## Call actions on mount
578
+ # Derived state
573
579
 
574
- When a hook that creates / binds a store is mounted, the **onMount** option is called.
580
+ A derived state is a state that can be deduced from state and props.
575
581
 
576
582
  ```typescript
577
583
  import { createStore } from 'state-decorator';
578
584
 
579
- const store = createStore(getInitialState, actionsImpl, {
580
- onMount: ({ a }) => {
581
- a.loadList;
582
- },
583
- });
584
-
585
- // onMount is called when this component is mounted
586
- function Container(p: ContainerProps) {
587
- useBindStore(store, p);
588
- }
589
- ```
585
+ type State = {
586
+ value: number;
587
+ };
590
588
 
591
- If an initial action is launched each time a property changes, consider using the **onMount** flag of the **onPropsChange** entry.
589
+ type Actions = {
590
+ add1: () => void;
591
+ };
592
592
 
593
- ## State sharing and slices
593
+ type Props = {
594
+ propIn: number;
595
+ };
594
596
 
595
- - Declare a store and bind it to a component using _useStore_ or _useBindStore_ hooks.
596
- - Deeper in the tree component, use _useStoreSlice_ to get a store slice.
597
- - Component using slices will be refreshed only if their store slice changes.
597
+ type DerivedState = {
598
+ derivedProp: number;
599
+ };
598
600
 
599
- ```typescript
600
- import React from 'react';
601
- import { useStore, useStoreSlice, StoreActions } from 'state-decorator';
601
+ const store = createStore<State, Actions, Props, DerivedState>(
602
+ () => ({ value: 0 }),
603
+ {
604
+ add1: ({ s }) => ({ ...s, value: s.value + 1 }),
605
+ },
606
+ {
607
+ derivedState: {
608
+ derivedProp: {
609
+ // get the list of dependencies to be checked to trigger the computation of the derived state
610
+ // if state.value AND/OR props.prop1 changes, derivedProp is recomputed
611
+ getDeps: ({ state, props }) => [state.value, props.propIn],
612
+ // compute derived state from state & props (use short aliases)
613
+ get: ({ s, p }) => s.value * p.propIn,
614
+ },
615
+ },
616
+ }
617
+ );
602
618
 
603
- // Declare typings & actions as above
619
+ export function App(props: Props) {
620
+ const { state } = useStore(store, props);
621
+ // state contains the store state and derived state
622
+ return <div>{state.derivedProp}</div>;
623
+ }
624
+ ```
604
625
 
605
- // Create a store
626
+ ## Recipes
606
627
 
607
- export const store = createStore(getInitialState, userAppActions);
628
+ - Derived state is the same as using _useMemo_ in sub component.
629
+ - If the derived state is needed in one component only, it may prove better to use a _useMemo_ in this component to save memory when the component is unmounted and store not.
630
+ - In general, do **not** compute derived values directly in state, it's error prone as you can forget some places or implement different logic.
631
+ - Use derived state especially if this state is shared accross several sub components
608
632
 
609
- // Bind to react component
633
+ # Call actions on mount
610
634
 
611
- export function Container(prop: Props) {
612
- const { state, actions } = useStore(store, props);
635
+ When a hook that creates / binds a store is mounted, the **onMount** option is called.
613
636
 
614
- // or
615
- // useBindStore(store, props);
616
- // if you are not interested in getting the state here
637
+ ```typescript
638
+ import { createStore } from 'state-decorator';
617
639
 
618
- return <div />;
619
- }
640
+ // global store
641
+ const store = createStore(getInitialState, actionsImpl, {
642
+ onMount: ({ a }) => {
643
+ a.loadList;
644
+ },
645
+ });
620
646
 
621
- // Components deeper in the component tree will be refreshed if, and only if,
622
- // slice is changed (here: text property)
647
+ store.init(initialProps);
648
+ ```
623
649
 
624
- export function SubComponent2() {
625
- const s = useStoreSlice(store, ['text']);
626
- return <div>{s.text}</div>;
627
- }
650
+ ```typescript
651
+ import { createStore } from 'state-decorator';
628
652
 
629
- export function SubComponent() {
630
- const s = useStoreSlice(store, (s) => ({ text: s.text }));
631
- return <div>{s.text}</div>;
653
+ // local store
654
+ function Container(p: ContainerProps) {
655
+ const store = useLocalStore(getInitialState, actionsImpl, p, {
656
+ onMount: ({ a }) => {
657
+ a.loadList;
658
+ },
659
+ });
632
660
  }
633
661
  ```
634
662
 
635
- [![Edit Slice](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/slices-v6-eg471?file=/src/SliceView.tsx)
636
-
637
- # Global configuration
638
-
639
- Overrides configuration to set properties that will be used by all stores.
640
-
641
- ```typescript
642
- // all parameters are optional
643
- setGlobalConfig({
644
- // Used to clone state and props when managing optimistic conflicting actions.
645
- clone: defaultCloneFunc,
646
- // Compare to objets and returns if they are equal (slices).
647
- comparator: shallow,
648
- // Callback function to handle asychronous actions rejected promise (error reporting).
649
- asyncErrorHandler: () => {},
650
- // Tests if the error will trigger a retry of the action or will fail directly (retry promises)
651
- retryOnErrorFunction: (error: Error) => error instanceof TypeError,
652
- // Notification function on successful asynchronous action (if success message is set on action)
653
- notifySuccess: undefined,
654
- // Notification function on failed asynchronous action (if error message is set on action)
655
- notifyError: undefined,
656
- // Notification function injected in side effects action context (success and error) to notify warning
657
- notifyWarning: undefined,
658
- // 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_.
659
- getErrorMessage: undefined,
660
- });
661
- ```
663
+ If an initial action is launched each time a property changes, consider using the **onMount** flag of the **onPropsChange** entry.
662
664
 
663
665
  # Update store when props change
664
666
 
@@ -672,7 +674,7 @@ const store = createStore(getInitialState, actionsImpl, {
672
674
  // list of depedencies that should trigger effects & side effects if changed
673
675
  getDeps: (p) => [p.id],
674
676
  // state changes, indices contains the indices of dependencies that have changed
675
- effects: ({ state, props, indices, isInit }) => ({ ...state, selectedId: props.id }),
677
+ effects: ({ props, indices, isInit }) => ({ selectedId: props.id }),
676
678
  // other changes (use short aliases)
677
679
  sideEffects: ({ p, a, indices, isInit }) => {
678
680
  a.loadItem(p.id);
@@ -708,7 +710,7 @@ const store = createStore(getInitialState, actionsImpl, {
708
710
  },
709
711
  {
710
712
  getDeps: (p) => [p.otherProp],
711
- effects: ({ state, props, indices }) => ({ ...state, otherProp: props.otherProp }),
713
+ effects: ({ props, indices }) => ({ otherProp: props.otherProp }),
712
714
  sideEffects: ({ p, a }) => {
713
715
  a.otherSideEffect(p.otherProp);
714
716
  },
@@ -731,7 +733,7 @@ const store = createStore(getInitialState, actionsImpl, {
731
733
  onPropsChange: [
732
734
  {
733
735
  getDeps: (p) => [p.userId],
734
- effects: ({ s, p }) => ({ ...s, userId: p.userId }),
736
+ effects: ({ p }) => ({ userId: p.userId }),
735
737
  sideEffects: ({ p, a }) => {
736
738
  a.loadUser(p.userId);
737
739
  },
@@ -745,7 +747,7 @@ const store = createStore(getInitialState, actionsImpl, {
745
747
  onPropsChange: [
746
748
  {
747
749
  getDeps: (p) => [p.userId],
748
- effects: ({ s, p, isInit }) => (isInit ? null : { ...s, userId: p.userId }),
750
+ effects: ({ p, isInit }) => (isInit ? null : { userId: p.userId }),
749
751
  sideEffects: ({ p, a }) => {
750
752
  a.loadUser(p.userId);
751
753
  },
@@ -762,60 +764,235 @@ const store = createStore(getInitialState, actionsImpl, {
762
764
  - Use several prop change configurations to separate dependencies between props and have simple definition.
763
765
  - Use onMount flag to have a more systematic and simpler code.
764
766
 
765
- # Derived state
767
+ # Global / local Stores and State sharing
766
768
 
767
- A derived state is a state that can be deduced from state and props.
769
+ There are two ways to share state and actions:
770
+
771
+ - Using regular props.
772
+ - Using state slices.
773
+
774
+ There are two types of stores:
775
+
776
+ - Global store:
777
+
778
+ - store is created as an instance (**createStore**) and it is exported from the JS module.
779
+ - it can be imported in any other JS module.
780
+ - store lifespan is equal to the application one.
781
+
782
+ - Local store:
783
+ - store is created by a hook (**useLocalStore**) and is bound to a React component (each instance of this component owns its own store).
784
+ - action and props can be shared using props or sharing the store itself (see example below)
785
+ - store is destroyed when its owner React component is unmounted.
786
+
787
+ ## Using props
788
+
789
+ - Bind a React component to make store state / actions available on React component.
790
+ - Any change in the store (state, loading actions, ...) will trigger a refresh of the bound React component.
791
+ - Pass state / actions using regular React props.
792
+ - Use React.memo to prevent unecessary React re-renders if needed.
793
+
794
+ ### Global store
768
795
 
769
796
  ```typescript
770
- import { createStore } from 'state-decorator';
797
+ // Declare typings & actions as above
771
798
 
772
- type State = {
773
- value: number;
774
- };
799
+ // Create a store in a dedicated file, named GlobalStore.ts for example
800
+ export const store = createStore(getInitialState, userAppActions);
775
801
 
776
- type Actions = {
777
- add1: () => void;
778
- };
802
+ //----------------------
779
803
 
780
- type Props = {
781
- propIn: number;
782
- };
804
+ // In another file
805
+ import React, { memo } from 'react';
806
+ import { useStore } from 'state-decorator';
807
+ import store from './GlobalStore';
783
808
 
784
- type DerivedState = {
785
- derivedProp: number;
786
- };
809
+ // Bind to react component
810
+ // Each time the store state changes, a re-render is done.
811
+ export function Container(props: Props) {
812
+ const { state, actions } = useStore(store, props);
813
+ return (
814
+ <div>
815
+ <Title text={state.title} />
816
+ <Subtitle text={state.subtitle} />
817
+ </div>
818
+ );
819
+ }
787
820
 
788
- const store = createStore<State, Actions, Props, DerivedState>(
789
- () => ({ value: 0 }),
790
- {
791
- add1: ({ s }) => ({ ...s, value: s.value + 1 }),
792
- },
793
- {
794
- derivedState: {
795
- derivedProp: {
796
- // get the list of dependencies to be checked to trigger the computation of the derived state
797
- // if state.value AND/OR props.prop1 changes, derivedProp is recomputed
798
- getDeps: ({ state, props }) => [state.value, props.propIn],
799
- // compute derived state from state & props (use short aliases)
800
- get: ({ s, p }) => s.value * p.propIn,
801
- },
802
- },
803
- }
804
- );
821
+ // Simple presentation components
822
+ // Use memo to prevent re-render if another part of the state has changed than "title"
823
+ const Title = memo(function Title(props: { title: string }) {
824
+ return <div>{props.text}</div>;
825
+ });
805
826
 
806
- export function App(props: Props) {
807
- const { state } = useStore(store, props);
808
- // state contains the store state and derived state
809
- return <div>{state.derivedProp}</div>;
827
+ const Subtitle = memo(function Subtitle(props: { subtitle: string }) {
828
+ return <div>{props.subtitle}</div>;
829
+ });
830
+ ```
831
+
832
+ ### Local Store
833
+
834
+ ```typescript
835
+ import React, { memo } from 'react';
836
+ import { useLocalStore } from 'state-decorator';
837
+
838
+ // Declare typings & actions as above
839
+
840
+ // Create and bind to react component
841
+ // Each time the store state changes, a re-render is done.
842
+ export function Container(props: Props) {
843
+ const { state, actions } = useLocalStore(getInitialState, userAppActions, props);
844
+ return (
845
+ <div>
846
+ <Title text={state.title} />
847
+ <Subtitle text={state.subtitle} />
848
+ </div>
849
+ );
810
850
  }
851
+
852
+ // Simple presentation components
853
+ // Use memo to prevent re-render if another part of the state has changed than "title"
854
+ const Title = memo(function Title(props: { title: string }) {
855
+ return <div>{props.title}</div>;
856
+ });
857
+
858
+ const Subtitle = memo(function Subtitle(props: { subtitle: string }) {
859
+ return <div>{props.subtitle}</div>;
860
+ });
811
861
  ```
812
862
 
813
- ## Recipes
863
+ ## State slices
814
864
 
815
- - Derived state is the same as using _useMemo_ in sub component.
816
- - If the derived state is needed in one component only, it may prove better to use a _useMemo_ in this component to save memory when the component is unmounted and store not.
817
- - In general, do **not** compute derived values directly in state, it's error prone as you can forget some places or implement different logic.
818
- - Use derived state especially if this state is shared accross several sub components
865
+ Using React props implies useless re-renders and optionally memoization.
866
+
867
+ To prevent this, React provides [context](https://en.reactjs.org/docs/context.html) hooks.
868
+
869
+ 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.
870
+
871
+ 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.
872
+
873
+ [![Edit Slice](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/slices-v6-eg471?file=/src/SliceView.tsx)
874
+
875
+ ## Global Store
876
+
877
+ ```typescript
878
+ // Declare typings & actions as above
879
+
880
+ // Create a store in a dedicated file, named GlobalStore.ts for example
881
+ export const store = createStore(getInitialState, userAppActions);
882
+
883
+ //----------------------
884
+
885
+ import React, { memo } from 'react';
886
+ import { useStoreSlice } from 'state-decorator';
887
+ import store from './GlobalStore';
888
+
889
+ export function Container(props: Props) {
890
+ return (
891
+ <div>
892
+ <Title />
893
+ <Subtitle />
894
+ </div>
895
+ );
896
+ }
897
+
898
+ function Title() {
899
+ // extracts the "title" slice
900
+ // component is refresh only if title is changed in the store
901
+ const { title } = useStoreSlice(store, ['title']);
902
+ return <div>{title}</div>;
903
+ }
904
+
905
+ function Subtitle(props: { subtitle: string }) {
906
+ const { subtitle } = useStoreSlice(store, ['subtitle']);
907
+ return <div>{subtitle}</div>;
908
+ }
909
+ ```
910
+
911
+ ## Local Store
912
+
913
+ ```typescript
914
+ // ------- stores/MyStore.ts --------
915
+
916
+ import React from 'react';
917
+ import { useLocalStore, useStoreContextSlice } from 'state-decorator';
918
+
919
+ // Declare typings & actions as above
920
+
921
+ // Create a store context
922
+
923
+ type StoreContextProps = StoreApi<State, Actions, Props>;
924
+
925
+ export const StoreContext = createContext<StoreContextProps>(null);
926
+
927
+ // The StoreContext will allow to access to the store
928
+ // But this context is NOT refreshed if the store state is changed
929
+ export function StoreContextProvider(p: { children: any; propIn: string }) {
930
+ const { children, ...props } = p;
931
+ const store = useLocalStore(getInitialState, actionsImpl, props);
932
+ return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
933
+ }
934
+
935
+ // In a container file ------------------------
936
+
937
+ import { StoreContextProvider } from 'stores/MyStore';
938
+
939
+ // If Container is destroyed store is destroyed
940
+ export function Container(props: Props) {
941
+ return (
942
+ <StoreContextProvider {...props}>
943
+ <SubComponent />
944
+ <SubComponent2 />
945
+ </StoreContextProvider>
946
+ );
947
+ }
948
+
949
+ // Component deeper in the component tree...
950
+
951
+ import { StoreContext } from 'stores/MyStore';
952
+
953
+ function Title() {
954
+ // extracts the "title" slice
955
+ // component is refresh only if title is changed in the store
956
+ const { title } = useStoreContextSlice(StoreContext, ['title']);
957
+
958
+ // same as
959
+ // const store = useContext(StoreContext)
960
+ // const { title } = useStoreSlice(store, ['title']);
961
+
962
+ return <div>{title}</div>;
963
+ }
964
+
965
+ function Subtitle(props: { subtitle: string }) {
966
+ const { subtitle } = useStoreContextSlice(StoreContext, ['subtitle']);
967
+ return <div>{subtitle}</div>;
968
+ }
969
+ ```
970
+
971
+ # Global configuration
972
+
973
+ Overrides configuration to set properties that will be used by all stores.
974
+
975
+ ```typescript
976
+ // all parameters are optional
977
+ setGlobalConfig({
978
+ // Used to clone state and props when managing optimistic conflicting actions.
979
+ clone: defaultCloneFunc,
980
+ // Compare to objets and returns if they are equal (slices).
981
+ comparator: shallow,
982
+ // Callback function to handle asychronous actions rejected promise (error reporting).
983
+ asyncErrorHandler: () => {},
984
+ // Tests if the error will trigger a retry of the action or will fail directly (retry promises)
985
+ retryOnErrorFunction: (error: Error) => error instanceof TypeError,
986
+ // Notification function on successful asynchronous action (if success message is set on action)
987
+ notifySuccess: undefined,
988
+ // Notification function on failed asynchronous action (if error message is set on action)
989
+ notifyError: undefined,
990
+ // Notification function injected in side effects action context (success and error) to notify warning
991
+ notifyWarning: undefined,
992
+ // 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_.
993
+ getErrorMessage: undefined,
994
+ });
995
+ ```
819
996
 
820
997
  # Debug actions
821
998
 
@@ -900,11 +1077,11 @@ function Container(props: Props) {
900
1077
  - a new store is created each time state or props are set.
901
1078
  - it allows to share mock store across tests.
902
1079
  - The API is chainable: functions are returning a mock store, excepting test functions.
903
- - On a mock store, we can test the store internals after initialiation, after inbound props have changed, after actions are called.
1080
+ - 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.
904
1081
 
905
1082
  ## Getting started
906
1083
 
907
- 1. Create and setup a mock store (_createMockFromStore_)
1084
+ 1. Create and setup a mock store (_createMockFromStore_, _createMockStore_)
908
1085
  2. Test store after initialization (_onInit_)
909
1086
  3. Test store after props changes (_onPropsChange_)
910
1087
  4. Test each action: