@tramvai/state 1.8.5 → 1.9.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.en.md +53 -0
- package/lib/connect/hooks/README.en.md +135 -0
- package/lib/createEvent/README.en.md +63 -0
- package/lib/createReducer/README.en.md +101 -0
- package/lib/devTools/README.en.md +24 -0
- package/lib/index.js +26 -26
- package/lib/index_middleware.js +8 -8
- package/package.json +2 -2
package/README.en.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# State
|
|
2
|
+
|
|
3
|
+
**State** is a library built into `tramvai` for managing application state.
|
|
4
|
+
|
|
5
|
+
## Peculiarities
|
|
6
|
+
|
|
7
|
+
- Redux-like state manager
|
|
8
|
+
- Built-in library similar to [redux-act](https://github.com/pauldijou/redux-act) to reduce boilerplate code
|
|
9
|
+
- Contains bindings to `react` components such as `connect` and `useSelector`
|
|
10
|
+
- Dynamic initialization of reducers. You can register a reducer at any time or generate a new one.
|
|
11
|
+
- Point subscriptions to changes in the states of reducers. When data changes, only the affected `connect` and `useSelector` are recalculated, not everything.
|
|
12
|
+
- Support for SSR mode.
|
|
13
|
+
|
|
14
|
+
## Basic concepts
|
|
15
|
+
|
|
16
|
+
- Store - A class that contains the state of all reducers, change subscriptions and is created for each client
|
|
17
|
+
- Reducers - entities in which we describe how data will be stored and transformed
|
|
18
|
+
- Events - events with which you can change the states of reducers
|
|
19
|
+
- Actions - functions that allow you to perform side effects and update data in the store. Similar to `redux-thunk`
|
|
20
|
+
|
|
21
|
+
## Recommendations
|
|
22
|
+
|
|
23
|
+
- You cannot mutate data in reducers. Otherwise, due to various optimizations, subscribers will not be notified about the changes.
|
|
24
|
+
- Initialize reducers as early as possible and before using it. Otherwise, when calling `dispatch(userLoadInformation())`, the reducer will not yet track events and will not receive data.
|
|
25
|
+
- Do not store static data in stores. Since this data will be transferred from the server to the client, the data will be duplicated. Better to put in constants.
|
|
26
|
+
- Break into small reducers. Otherwise, we have a huge reducer that contains a large amount of information and any changes will cause recalculations for a large number of components.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm i --save @tramvai/state
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Basic example
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { createReducer, createEvent } from '@tramvai/state';
|
|
38
|
+
|
|
39
|
+
export const userLoadInformation = createEvent('user load information');
|
|
40
|
+
export const userAddInformation = createEvent('user add information');
|
|
41
|
+
|
|
42
|
+
const userReducer = createReducer('user', {
|
|
43
|
+
info: {},
|
|
44
|
+
})
|
|
45
|
+
.on(userLoadInformation, (state, info) => ({ info }))
|
|
46
|
+
.on(userAddInformation, (state, { name, info }) => ({
|
|
47
|
+
...state,
|
|
48
|
+
state: {
|
|
49
|
+
...state.info,
|
|
50
|
+
[name]: info,
|
|
51
|
+
},
|
|
52
|
+
}));
|
|
53
|
+
```
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# State hooks
|
|
2
|
+
|
|
3
|
+
## useActions
|
|
4
|
+
|
|
5
|
+
Allows to execute tram [actions](concepts/action.md) in React components
|
|
6
|
+
|
|
7
|
+
### Interface
|
|
8
|
+
|
|
9
|
+
- `actions` - one or an array of tram actions
|
|
10
|
+
|
|
11
|
+
> If you pass an array to `useActions`, for typing you need to specify `as const` - `useActions([] as const)`
|
|
12
|
+
|
|
13
|
+
### Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { useActions } from '@tramvai/state';
|
|
17
|
+
import { loadUserAction, getInformationAction, setInformationAction } from './actions';
|
|
18
|
+
|
|
19
|
+
export const Component = () => {
|
|
20
|
+
// if you pass one action, the payload type for loadUser is automatically deduced
|
|
21
|
+
const loadUser = useActions(loadUserAction);
|
|
22
|
+
|
|
23
|
+
// if you pass a list of actions, `as const` is required for correct type inference
|
|
24
|
+
const [getInformation, setInformation] = useActions([
|
|
25
|
+
getInformationAction,
|
|
26
|
+
setInformationAction,
|
|
27
|
+
] as const);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<div onClick={loadUser}>load user</div>
|
|
32
|
+
<div onClick={getInformation}>get information</div>
|
|
33
|
+
<div onClick={() => setInformation({ user: 1 })}>set information</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## useSelector ()
|
|
40
|
+
|
|
41
|
+
Receiving data from the store in components
|
|
42
|
+
|
|
43
|
+
### Interface
|
|
44
|
+
|
|
45
|
+
- `stores: []` - a list of tokens that the selector will subscribe to. Will affect which store changes will trigger an update in the component
|
|
46
|
+
- `selector: (state) => any` - the selector itself, this is a function that will be called upon initialization and any changes to the stores passed to `stores`. The function should return data that can be used in the component
|
|
47
|
+
- `equalityFn?: (cur, prev) => boolean` - optional function to change the way of comparing past and new values of a selector
|
|
48
|
+
|
|
49
|
+
### Usage
|
|
50
|
+
|
|
51
|
+
To get data from a store, you can use a store name, a reference to a store, or an object with an optional store:
|
|
52
|
+
|
|
53
|
+
- `'storeName'`
|
|
54
|
+
- `storeObject`
|
|
55
|
+
- `{ store: storeObject, optional: true }`
|
|
56
|
+
- `{ store: 'storeName', optional: true }`
|
|
57
|
+
|
|
58
|
+
You can pass an array of keys, then for correct type inference it is better to use `as const`:
|
|
59
|
+
|
|
60
|
+
- `useSelector(['fooStoreName', barStoreObject] as const, ({ foo, bar }) => null)`;
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { useSelector } from '@tramvai/state';
|
|
64
|
+
|
|
65
|
+
export const Component = () => {
|
|
66
|
+
const isBrowser = useSelector('media', (state) => state.media.isBrowser);
|
|
67
|
+
|
|
68
|
+
return <div>{isBrowser ? <span>Browser</span> : <span>Desktop</span>}</div>;
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Optimizations
|
|
73
|
+
|
|
74
|
+
In order to reduce the number of component redrawings, after each call to `selector`, the return values are checked against those that were before. If the returned selector data has not changed, then the component will not be redrawn.
|
|
75
|
+
|
|
76
|
+
For this reason, it is better to get small chunks of information in selectors. Then there is less chance that the component will be updated. For example: we need the user's `roles`, we write a selector that requests all user data `(state) => state.user` and now any changes to the `user` reducer will update the component. It is better if we receive only the necessary data `(state) => state.user.roles`, in which case the component will be redrawn only when the user's `roles` change
|
|
77
|
+
|
|
78
|
+
## useStoreSelector
|
|
79
|
+
|
|
80
|
+
A simplified version of the useSelector hook into which only one store can be passed, created via createReducer. It was made to improve the inference of selector types, since useSelector itself cannot do this due to the use of strings, tokens and BaseStore heirs inside string names
|
|
81
|
+
|
|
82
|
+
### Interface
|
|
83
|
+
|
|
84
|
+
- `store: Reducer` - Store created through createReducer
|
|
85
|
+
- `selector: (state) => any` - the selector itself, this is a function that will be called upon initialization and any changes to the store passed to `stores`. The function should return data that can be used in the component
|
|
86
|
+
|
|
87
|
+
### Usage
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { useStoreSelector } from '@tramvai/state';
|
|
91
|
+
import { createReducer } from '@tramvai/state';
|
|
92
|
+
|
|
93
|
+
const myStore = createReducer('myStore', { id: '123' });
|
|
94
|
+
|
|
95
|
+
export const Component = () => {
|
|
96
|
+
const id = useStoreSelector((myStore, (state) => state.id)); // The id type will be correctly inferred as "string"
|
|
97
|
+
|
|
98
|
+
return <div>{id}</div>;
|
|
99
|
+
};
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Optimizations
|
|
103
|
+
|
|
104
|
+
The hook is a wrapper over useSelector, so the optimizations are the same. The selector function itself is memoized inside
|
|
105
|
+
|
|
106
|
+
## useStore
|
|
107
|
+
|
|
108
|
+
Hook to get the state of a specific reducer.
|
|
109
|
+
|
|
110
|
+
Peculiarities:
|
|
111
|
+
|
|
112
|
+
- automatically displays the type of state
|
|
113
|
+
- re-renders the component only when the reducer is updated
|
|
114
|
+
- allows you to create reducers "on the fly"
|
|
115
|
+
|
|
116
|
+
### Interface
|
|
117
|
+
|
|
118
|
+
- `store: Reducer` - Store created by createReducer
|
|
119
|
+
|
|
120
|
+
### Usage
|
|
121
|
+
|
|
122
|
+
Basic example:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { useStore } from '@tramvai/state';
|
|
126
|
+
import { createReducer } from '@tramvai/state';
|
|
127
|
+
|
|
128
|
+
const userReducer = createReducer('user', { id: '123' });
|
|
129
|
+
|
|
130
|
+
export const Component = () => {
|
|
131
|
+
const { id } = useStore(userReducer);
|
|
132
|
+
|
|
133
|
+
return <div>{id}</div>;
|
|
134
|
+
};
|
|
135
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# createEvent
|
|
2
|
+
|
|
3
|
+
The `createEvent` method creates an event that can be subscribed to in state management
|
|
4
|
+
|
|
5
|
+
## Method Description
|
|
6
|
+
|
|
7
|
+
`createEvent(eventName: string, payloadCreator?: PayloadTransformer): EventCreator`
|
|
8
|
+
|
|
9
|
+
- `eventName` - Unique identifier of the event
|
|
10
|
+
- `payloadCreator` - an optional function that allows you to combine multiple arguments into one, In cases where the event was called with multiple arguments.
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
#### Creating an event without parameters
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { createEvent } from '@tramvai/state';
|
|
18
|
+
|
|
19
|
+
const userLoadingInformation = createEvent('user loading information');
|
|
20
|
+
|
|
21
|
+
userLoadingInformation();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
#### Creating an event with parameters
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { createEvent } from '@tramvai/state';
|
|
28
|
+
|
|
29
|
+
const userInformation = createEvent<{ age: number; name: string }>('user information');
|
|
30
|
+
|
|
31
|
+
userInformation({ age: 42, name: 'Tom' });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Create event with payload conversion
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { createEvent } from '@tramvai/state';
|
|
38
|
+
|
|
39
|
+
const itemPrice = createEvent('user information', (info: string, price: number) => ({
|
|
40
|
+
[info]: price,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
itemPrice('car', 3000);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### Using Events in Actions
|
|
47
|
+
|
|
48
|
+
We create an action in which, after loading the information, we create an event and throw it into context.dispatch
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { createAction } from '@tramvai/core';
|
|
52
|
+
import { createEvent } from '@tramvai/state';
|
|
53
|
+
|
|
54
|
+
const userInformation = createEvent < { age: number, name: string } > 'user information';
|
|
55
|
+
|
|
56
|
+
const action = createAction({
|
|
57
|
+
name: 'userLoadInformation',
|
|
58
|
+
fn: async (context) => {
|
|
59
|
+
const result = await tinkoffRequest({ method: 'information' });
|
|
60
|
+
context.dispatch(userInformation(result));
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# createReducer
|
|
2
|
+
|
|
3
|
+
The `createReducer` method creates reducer functions that describe the state during initialization and the reaction to state changes.
|
|
4
|
+
|
|
5
|
+
The working principle and api is built based on: https://redux.js.org/basics/reducers and the use interface from https://github.com/pauldijou/redux-act#createreducerhandlers-defaultstate
|
|
6
|
+
|
|
7
|
+
### Method Description
|
|
8
|
+
|
|
9
|
+
`createReducer(name, initialState)`
|
|
10
|
+
|
|
11
|
+
- `name` - unique name of the reducer. Should not overlap with other reducers
|
|
12
|
+
- `initialState` - default reducer state
|
|
13
|
+
|
|
14
|
+
### Typing
|
|
15
|
+
|
|
16
|
+
By default, the reducer state type and name are displayed automatically:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { createReducer } from '@tramvai/state';
|
|
20
|
+
|
|
21
|
+
const userReducer = createReducer('user', { name: 'anonymus' });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Why do we need typing for the name of the reducer at all?
|
|
25
|
+
Then this reducer will be more convenient to use together with `useSelector`.
|
|
26
|
+
|
|
27
|
+
If you pass the state type manually, it is desirable to specify the name as the second argument of the generic:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { createReducer } from '@tramvai/state';
|
|
31
|
+
|
|
32
|
+
type UserState = { name: string };
|
|
33
|
+
|
|
34
|
+
const userReducer = createReducer<UserState, 'user'>('user', { name: 'anonymus' });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
But, you can simply set the desired type for `initialState`:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { createReducer } from '@tramvai/state';
|
|
41
|
+
|
|
42
|
+
type UserState = { name?: string };
|
|
43
|
+
|
|
44
|
+
const userReducer = createReducer('user', {} as UserState);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Subscription to events
|
|
48
|
+
|
|
49
|
+
`.on(evnet, reducer)` When creating a reducer, the .on method becomes available, which allows you to subscribe to events and return a new state
|
|
50
|
+
|
|
51
|
+
- `event` - an event or a string to which the reducer will be subscribed
|
|
52
|
+
- `reducer(state, payload)` - a pure function that takes the current `state`, `payload` from the event and must return the new state of the reducer
|
|
53
|
+
|
|
54
|
+
**_An example of using the `.on` method_**
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import { createReducer, createEvent } from '@tramvai/state';
|
|
58
|
+
|
|
59
|
+
export const userLoadInformation = createEvent < { status: string } > 'user load information';
|
|
60
|
+
export const userAddInformation = createEvent < { name: string, info: {} } > 'user add information';
|
|
61
|
+
|
|
62
|
+
const userReducer = createReducer('user', {
|
|
63
|
+
info: {},
|
|
64
|
+
})
|
|
65
|
+
.on(userLoadInformation, (state, info) => ({ info }))
|
|
66
|
+
.on(userAddInformation, (state, { name, info }) => ({
|
|
67
|
+
...state,
|
|
68
|
+
state: {
|
|
69
|
+
...state.info,
|
|
70
|
+
[name]: info,
|
|
71
|
+
},
|
|
72
|
+
}));
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Automatic creation of events
|
|
76
|
+
|
|
77
|
+
`.createEvents(model)` method that removes the need to create and explicitly bind events
|
|
78
|
+
|
|
79
|
+
- `model` - an object in which the key is the event identifier, which will then be passed to `createEvent`, and the value is the reducer function, which will get into the `.on()` method and will be called when the events are triggered
|
|
80
|
+
|
|
81
|
+
**_An example of using the `.createEvents` method_**
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { createReducer } from '@tramvai/state';
|
|
85
|
+
|
|
86
|
+
const userReducer = createReducer('user', {
|
|
87
|
+
info: {},
|
|
88
|
+
});
|
|
89
|
+
export const { userLoadInformation, userAddInformation } = userReducer.createEvents({
|
|
90
|
+
userLoadInformation: (state, info: { status: string }) => ({ info }),
|
|
91
|
+
userAddInformation: (state, { name, info }: { name: string; info: {} }) => ({
|
|
92
|
+
...state,
|
|
93
|
+
state: {
|
|
94
|
+
...state.info,
|
|
95
|
+
[name]: info,
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
It is imperative to describe the types of the `payload` argument in reducers, otherwise type inference for events will not work.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Redux devtools
|
|
2
|
+
|
|
3
|
+
To enable devtools, you need to run:
|
|
4
|
+
|
|
5
|
+
- Install browser extension: [Chrome extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) or [FireFox extension](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/)
|
|
6
|
+
- Open the page on `tramvai` and open the extension by clicking on the Redux devtools icon
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
### Possible problems
|
|
11
|
+
|
|
12
|
+
1. For a better user experience, you need to use a separate redux dev tools extension window, not a tab in chrome developer tools, because otherwise the action history is not saved, see [issue](https://github.com/zalmoxisus/redux-devtools-extension/issues/505).
|
|
13
|
+
|
|
14
|
+
### Performance
|
|
15
|
+
|
|
16
|
+
Since the entire state of the application with all the actions is quite large, there are noticeable brakes when working with devtools when using jumps over states/events and when a large number of actions are triggered simultaneously. That's why:
|
|
17
|
+
|
|
18
|
+
1. Use customization techniques to set pickState to reduce the size of data in devtools.
|
|
19
|
+
1. Increase the value of the latency parameter (passed to connectViaExtension.connect), which essentially debounces sending actions to the extension, see [docs](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#latency)
|
|
20
|
+
|
|
21
|
+
### Additional links
|
|
22
|
+
|
|
23
|
+
- [Devtools repository](https://github.com/zalmoxisus/redux-devtools-extension)
|
|
24
|
+
- [Getting Started with Redux DevTools Extension ](https://egghead.io/lessons/javascript-getting-started-with-redux-dev-tools)
|
package/lib/index.js
CHANGED
|
@@ -169,7 +169,7 @@ class DispatcherContext extends SimpleEmitter {
|
|
|
169
169
|
subscribe: this.subscribe.bind(this),
|
|
170
170
|
dispatch: (...args) => dispatch(...args),
|
|
171
171
|
};
|
|
172
|
-
dispatch = compose__default[
|
|
172
|
+
dispatch = compose__default["default"](...middlewares.map((middleware) => middleware(api)))(this.applyDispatch);
|
|
173
173
|
return dispatch;
|
|
174
174
|
}
|
|
175
175
|
// eslint-disable-next-line max-statements
|
|
@@ -468,9 +468,9 @@ function createDispatcher(options) {
|
|
|
468
468
|
return new Dispatcher(options);
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
-
function createEvent(eventName, payloadCreator = identity__default[
|
|
471
|
+
function createEvent(eventName, payloadCreator = identity__default["default"]) {
|
|
472
472
|
if (process.env.NODE_ENV !== 'production') {
|
|
473
|
-
if (!isString__default[
|
|
473
|
+
if (!isString__default["default"](eventName) || !eventName.length) {
|
|
474
474
|
throw new Error(`eventName should be not empty string, got '${eventName}'`);
|
|
475
475
|
}
|
|
476
476
|
}
|
|
@@ -555,7 +555,7 @@ class BaseStore extends SimpleEmitter {
|
|
|
555
555
|
return this.state;
|
|
556
556
|
}
|
|
557
557
|
dehydrate() {
|
|
558
|
-
return pick__default[
|
|
558
|
+
return pick__default["default"](Object.keys(this.hydrateKeys), this.state);
|
|
559
559
|
}
|
|
560
560
|
rehydrate(state) {
|
|
561
561
|
this.setStateSilently(state);
|
|
@@ -637,7 +637,7 @@ function useActions(actions) {
|
|
|
637
637
|
const context = useConsumerContext();
|
|
638
638
|
const actionsRef = reactHooks.useShallowEqual(actions);
|
|
639
639
|
return React.useMemo(() => {
|
|
640
|
-
if (isArray__default[
|
|
640
|
+
if (isArray__default["default"](actionsRef)) {
|
|
641
641
|
return actionsRef.map((action) => context.executeAction.bind(context, action));
|
|
642
642
|
}
|
|
643
643
|
return context.executeAction.bind(context, actionsRef);
|
|
@@ -659,7 +659,7 @@ function useStore(reducer) {
|
|
|
659
659
|
const stateRef = React.useRef();
|
|
660
660
|
const reducerRef = React.useRef(reducer);
|
|
661
661
|
const addedReducerRef = React.useRef(null);
|
|
662
|
-
const unsubscribeRef = React.useRef(noop__default[
|
|
662
|
+
const unsubscribeRef = React.useRef(noop__default["default"]);
|
|
663
663
|
const [, forceRender] = React.useReducer((s) => s + 1, 0);
|
|
664
664
|
React.useMemo(() => {
|
|
665
665
|
// отписываемся от обновлений текущего редьюсера
|
|
@@ -713,13 +713,13 @@ function scheduling() {
|
|
|
713
713
|
}
|
|
714
714
|
|
|
715
715
|
const schedule = scheduling();
|
|
716
|
-
function useSelector(storesOrStore, selector, equalityFn = shallowEqual__default[
|
|
717
|
-
invariant__default[
|
|
716
|
+
function useSelector(storesOrStore, selector, equalityFn = shallowEqual__default["default"]) {
|
|
717
|
+
invariant__default["default"](selector, `You must pass a selector to useSelectors`);
|
|
718
718
|
const context = useConsumerContext();
|
|
719
719
|
const [, forceRender] = React.useReducer((s) => s + 1, 0);
|
|
720
720
|
const renderIsScheduled = React.useRef(false);
|
|
721
721
|
const storesRef = reactHooks.useShallowEqual(storesOrStore);
|
|
722
|
-
const subscription = React.useMemo(() => new Subscription(toArray__default[
|
|
722
|
+
const subscription = React.useMemo(() => new Subscription(toArray__default["default"](storesRef).map(context.getStore)), [storesRef, context]);
|
|
723
723
|
const latestSubscriptionCallbackError = React.useRef();
|
|
724
724
|
const latestSelector = React.useRef(selector);
|
|
725
725
|
const latestSelectedState = React.useRef();
|
|
@@ -845,7 +845,7 @@ pure = true,
|
|
|
845
845
|
...connectOptions } = {}) {
|
|
846
846
|
return function wrapWithConnect(WrappedComponent) {
|
|
847
847
|
if (process.env.NODE_ENV !== 'production') {
|
|
848
|
-
invariant__default[
|
|
848
|
+
invariant__default["default"](reactIs.isValidElementType(WrappedComponent), `You must pass a component to the function returned by ` +
|
|
849
849
|
`${methodName}. Instead received ${stringifyComponent(WrappedComponent)}`);
|
|
850
850
|
}
|
|
851
851
|
const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
|
@@ -875,7 +875,7 @@ pure = true,
|
|
|
875
875
|
}, [props]);
|
|
876
876
|
// Retrieve the store and ancestor subscription via context, if available
|
|
877
877
|
const contextValue = useConsumerContext();
|
|
878
|
-
invariant__default[
|
|
878
|
+
invariant__default["default"](Boolean(contextValue), `Could not find context in ` +
|
|
879
879
|
`"${displayName}". Either wrap the root component in a <Provider>, ` +
|
|
880
880
|
`or pass a custom React context provider to <Provider> and the corresponding ` +
|
|
881
881
|
`React context consumer to ${displayName} in connect options.`);
|
|
@@ -1011,43 +1011,43 @@ pure = true,
|
|
|
1011
1011
|
// We memoize the elements for the rendered child component as an optimization.
|
|
1012
1012
|
const renderedWrappedComponent = React.useMemo(
|
|
1013
1013
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
1014
|
-
() => React__default[
|
|
1014
|
+
() => React__default["default"].createElement(WrappedComponent, Object.assign({}, actualChildProps, { ref: forwardedRef })), [forwardedRef, actualChildProps]);
|
|
1015
1015
|
return renderedWrappedComponent;
|
|
1016
1016
|
};
|
|
1017
1017
|
// If we're in "pure" mode, ensure our wrapper component only re-renders when incoming props have changed.
|
|
1018
|
-
const Connect = pure ? React__default[
|
|
1018
|
+
const Connect = pure ? React__default["default"].memo(ConnectFunction) : ConnectFunction;
|
|
1019
1019
|
Connect.WrappedComponent = WrappedComponent;
|
|
1020
1020
|
Connect.displayName = displayName;
|
|
1021
1021
|
if (forwardRef) {
|
|
1022
|
-
const forwarded = React__default[
|
|
1022
|
+
const forwarded = React__default["default"].forwardRef(function forwardConnectRef(props, ref) {
|
|
1023
1023
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
1024
|
-
return React__default[
|
|
1024
|
+
return React__default["default"].createElement(Connect, Object.assign({}, props, { forwardedRef: ref }));
|
|
1025
1025
|
});
|
|
1026
1026
|
forwarded.displayName = displayName;
|
|
1027
1027
|
forwarded.WrappedComponent = WrappedComponent;
|
|
1028
|
-
return hoistStatics__default[
|
|
1028
|
+
return hoistStatics__default["default"](forwarded, WrappedComponent);
|
|
1029
1029
|
}
|
|
1030
|
-
return hoistStatics__default[
|
|
1030
|
+
return hoistStatics__default["default"](Connect, WrappedComponent);
|
|
1031
1031
|
};
|
|
1032
1032
|
}
|
|
1033
1033
|
|
|
1034
1034
|
const getLogger = () => console;
|
|
1035
1035
|
|
|
1036
1036
|
function verifyPlainObject(value, displayName, methodName) {
|
|
1037
|
-
if (!isPlainObject__default[
|
|
1037
|
+
if (!isPlainObject__default["default"](value)) {
|
|
1038
1038
|
getLogger().debug(`${methodName}() in ${displayName} must return a plain object. Instead received ${value}.`);
|
|
1039
1039
|
}
|
|
1040
1040
|
}
|
|
1041
1041
|
|
|
1042
1042
|
function verifyFunction(value, displayName, methodName) {
|
|
1043
|
-
if (!isFunction__default[
|
|
1043
|
+
if (!isFunction__default["default"](value)) {
|
|
1044
1044
|
getLogger().debug(`${methodName}() in ${displayName} must be a function. Instead received ${JSON.stringify(value)}.`);
|
|
1045
1045
|
}
|
|
1046
1046
|
}
|
|
1047
1047
|
|
|
1048
1048
|
function wrapMapToPropsConstant(getConstant) {
|
|
1049
1049
|
return function initConstantSelector(context, options) {
|
|
1050
|
-
const constantSelector = always__default[
|
|
1050
|
+
const constantSelector = always__default["default"](getConstant(context, options));
|
|
1051
1051
|
constantSelector.dependsOnOwnProps = false;
|
|
1052
1052
|
return constantSelector;
|
|
1053
1053
|
};
|
|
@@ -1102,7 +1102,7 @@ function wrapMapToPropsFunc(mapToProps, methodName) {
|
|
|
1102
1102
|
}
|
|
1103
1103
|
function wrapMapToPropsObject(actionsObject) {
|
|
1104
1104
|
function getConstant({ executeAction }, { displayName }) {
|
|
1105
|
-
return mapObject__default[
|
|
1105
|
+
return mapObject__default["default"]((action, actionName) => {
|
|
1106
1106
|
if (process.env.NODE_ENV !== 'production') {
|
|
1107
1107
|
verifyFunction(action, displayName, actionName);
|
|
1108
1108
|
}
|
|
@@ -1118,7 +1118,7 @@ function whenMapContextToPropsIsFunction(mapContextToProps) {
|
|
|
1118
1118
|
: undefined;
|
|
1119
1119
|
}
|
|
1120
1120
|
function whenMapContextToPropsIsMissing(mapContextToProps) {
|
|
1121
|
-
return !mapContextToProps ? wrapMapToPropsConstant(always__default[
|
|
1121
|
+
return !mapContextToProps ? wrapMapToPropsConstant(always__default["default"]({})) : undefined;
|
|
1122
1122
|
}
|
|
1123
1123
|
function whenMapContextToPropsIsObject(mapContextToProps) {
|
|
1124
1124
|
return typeof mapContextToProps === 'object' && mapContextToProps !== null
|
|
@@ -1137,7 +1137,7 @@ function whenMapStateToPropsIsFunction(mapStateToProps) {
|
|
|
1137
1137
|
: undefined;
|
|
1138
1138
|
}
|
|
1139
1139
|
function whenMapStateToPropsIsMissing(mapStateToProps) {
|
|
1140
|
-
return !mapStateToProps ? wrapMapToPropsConstant(always__default[
|
|
1140
|
+
return !mapStateToProps ? wrapMapToPropsConstant(always__default["default"]({})) : undefined;
|
|
1141
1141
|
}
|
|
1142
1142
|
const mapStateToPropsFactories = [
|
|
1143
1143
|
whenMapStateToPropsIsFunction,
|
|
@@ -1181,7 +1181,7 @@ const verifyMapToProps = (mapStateToProps, name) => {
|
|
|
1181
1181
|
function verify(state, props) {
|
|
1182
1182
|
const firstStateProps = mapStateToProps(state, props);
|
|
1183
1183
|
const secondStateProps = mapStateToProps(state, props);
|
|
1184
|
-
if (!shallowEqual__default[
|
|
1184
|
+
if (!shallowEqual__default["default"](firstStateProps, secondStateProps)) {
|
|
1185
1185
|
getLogger().warn('Component "%s" recreate equal props %s', name, Object.keys(firstStateProps)
|
|
1186
1186
|
.filter((key) => firstStateProps[key] !== secondStateProps[key])
|
|
1187
1187
|
.map((key) => `"${key}"`)
|
|
@@ -1286,7 +1286,7 @@ function finalPropsSelectorFactory(context, { initMapStateToProps, initMapContex
|
|
|
1286
1286
|
}
|
|
1287
1287
|
|
|
1288
1288
|
const Provider = ({ context, children }) => {
|
|
1289
|
-
return React__default[
|
|
1289
|
+
return React__default["default"].createElement(ConnectContext.Provider, { value: context }, children);
|
|
1290
1290
|
};
|
|
1291
1291
|
|
|
1292
1292
|
/*
|
|
@@ -1316,7 +1316,7 @@ function match(arg, factories, name) {
|
|
|
1316
1316
|
// createConnect with default args builds the 'official' connect behavior. Calling it with
|
|
1317
1317
|
// different options opens up some testing and extensibility scenarios
|
|
1318
1318
|
function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories: mapStateToPropsFactories$1 = mapStateToPropsFactories, mapContextToPropsFactories: mapContextToPropsFactories$1 = mapContextToPropsFactories, mergePropsFactories: mergePropsFactories$1 = mergePropsFactories, selectorFactory = finalPropsSelectorFactory, schedule = scheduling(), } = {}) {
|
|
1319
|
-
return (stores, mapStateToProps, mapContextToProps, mergeProps, { pure = true, areStatesEqual = strictEqual__default[
|
|
1319
|
+
return (stores, mapStateToProps, mapContextToProps, mergeProps, { pure = true, areStatesEqual = strictEqual__default["default"], areOwnPropsEqual = shallowEqual__default["default"], areStatePropsEqual = shallowEqual__default["default"], areMergedPropsEqual = shallowEqual__default["default"], ...extraOptions } = {}
|
|
1320
1320
|
// eslint-disable-next-line max-params
|
|
1321
1321
|
) => {
|
|
1322
1322
|
// @ts-ignore
|
package/lib/index_middleware.js
CHANGED
|
@@ -14,13 +14,13 @@ var noop__default = /*#__PURE__*/_interopDefaultLegacy(noop);
|
|
|
14
14
|
var startsWith__default = /*#__PURE__*/_interopDefaultLegacy(startsWith);
|
|
15
15
|
var isElement__default = /*#__PURE__*/_interopDefaultLegacy(isElement);
|
|
16
16
|
|
|
17
|
-
const elementReplacer = pick__default[
|
|
17
|
+
const elementReplacer = pick__default["default"](['tagName', 'id', 'className']);
|
|
18
18
|
// eslint-disable-next-line import/no-mutable-exports
|
|
19
19
|
let devTools = {
|
|
20
|
-
init: noop__default[
|
|
21
|
-
send: noop__default[
|
|
22
|
-
subscribe: noop__default[
|
|
23
|
-
error: noop__default[
|
|
20
|
+
init: noop__default["default"],
|
|
21
|
+
send: noop__default["default"],
|
|
22
|
+
subscribe: noop__default["default"],
|
|
23
|
+
error: noop__default["default"],
|
|
24
24
|
};
|
|
25
25
|
// eslint-disable-next-line no-underscore-dangle,@typescript-eslint/no-explicit-any
|
|
26
26
|
if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
|
|
@@ -33,9 +33,9 @@ if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
|
|
|
33
33
|
replacer: (key, value) => {
|
|
34
34
|
// eslint-disable-next-line default-case
|
|
35
35
|
switch (true) {
|
|
36
|
-
case isElement__default[
|
|
36
|
+
case isElement__default["default"](value):
|
|
37
37
|
return elementReplacer(value);
|
|
38
|
-
case startsWith__default[
|
|
38
|
+
case startsWith__default["default"]('_reactInternal', `${key}`):
|
|
39
39
|
return '_reactInternal';
|
|
40
40
|
}
|
|
41
41
|
return value;
|
|
@@ -47,7 +47,7 @@ if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
|
|
|
47
47
|
const DISPATCH = 'DISPATCH';
|
|
48
48
|
|
|
49
49
|
const middleware = ({ pickState } = {}) => (api) => (next) => {
|
|
50
|
-
const getState = pickState && pickState.length ? () => pick__default[
|
|
50
|
+
const getState = pickState && pickState.length ? () => pick__default["default"](pickState, api.getState()) : api.getState;
|
|
51
51
|
// TODO: типизировать message
|
|
52
52
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
53
|
devTools.subscribe((message) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/state",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@tinkoff/react-hooks": "0.0.22",
|
|
22
22
|
"@tinkoff/utils": "^2.1.2",
|
|
23
|
-
"@tramvai/types-actions-state-context": "1.
|
|
23
|
+
"@tramvai/types-actions-state-context": "1.9.0",
|
|
24
24
|
"@types/hoist-non-react-statics": "^3.3.1",
|
|
25
25
|
"invariant": "^2.2.4",
|
|
26
26
|
"react-is": "^17.0.1",
|