1- * [ Installation] ( #install )
2- * [ How to use controllers] ( #usage )
3- * [ Register dependencies] ( #controller-di )
4- * [ Without decorators] ( #without-decorators )
5- * [ Action creating] ( #action-creating )
6-
7- ### <a name =" install " ></a > Installation
8-
9- ``` bash
10- npm install @tomas_light/react-redux-controller
1+ # redux-controller-middleware
2+
3+ Adjust Redux middleware to be able to use controllers with Dependency Injection to handle actions.
4+
5+ [ ![ license] ( https://img.shields.io/badge/license-MIT-blue.svg )] ( https://github.com/mui/material-ui/blob/HEAD/LICENSE )
6+ [ ![ npm latest package] ( https://img.shields.io/npm/v/redux-controller-middleware/latest.svg )] ( https://img.shields.io/npm/v/redux-controller-middleware/latest.svg )
7+ [ ![ codecov] ( https://codecov.io/github/tomas-light/redux-controller-middleware/branch/main/graph/badge.svg?token=NuAoioGPVD )] ( https://codecov.io/github/redux-controller-middleware )
8+
9+ * [ Installation] ( #installation )
10+ * [ How to use] ( #how-to-use )
11+ * [ Custom action names] ( #custom-action-names )
12+ * [ Dependency injection] ( #dependency-injection )
13+ * [ createAction] ( #createAction )
14+ * [ Chaining actions] ( #chaining-action )
15+ * [ Without decorators] ( #without-decorators )
16+
17+ ## <a name =" installation " ></a > Installation
18+ npm
19+ ``` cmd
20+ npm install redux-controller-middleware
21+ ```
22+ yarn
23+ ``` cmd
24+ yarn add redux-controller-middleware
1125```
1226
13- ### <a name =" usage " ></a > How to use controllers
27+ ## <a name =" how-to-use " ></a > How to use
1428
15- Controller - is place for piece of logic in your application.
29+ Controller - is a place for a piece of logic in your application.
1630The differences from Saga (in ` redux-saga ` ) is your methods is not static!
17- It allows you to use dependency injection technics and simplify tests in some cases .
31+ It allows you to use dependency injection technics and simplify tests.
1832
1933Create your store
2034``` ts
2135// User.store.ts
22- import { Reducer } from ' @tomas_light/react- redux-controller' ;
36+ import { createReducer } from ' redux-controller-middleware ' ;
2337
2438class UserStore {
2539 users: string [] = [];
2640 usersAreLoading = false ;
2741
2842 static update = ' USER_update_store' ;
29- static reducer = Reducer (new UserStore (), UserStore .update );
43+ static reducer = createReducer (new UserStore (), UserStore .update );
3044}
3145```
3246
3347Register store reducer and add react-redux-controller middleware to redux.
3448You can also register DI container, that allows you to inject services in controllers.
3549``` ts
36- // configRedux .ts
37- import { combineReducers } from " redux " ;
38- import { configureStore } from " @reduxjs/toolkit " ;
39- import { controllerMiddleware } from " @tomas_light/react- redux-controller " ;
40- import { container } from " cheap-di " ;
50+ // configureRedux .ts
51+ import { configureStore } from ' @reduxjs/toolkit ' ;
52+ import { container } from ' cheap-di ' ;
53+ import { combineReducers } from ' redux' ;
54+ import { controllerMiddleware } from ' redux-controller-middleware ' ;
4155
42- import { UserStore } from " ./User.store" ;
56+ import { UserStore } from ' ./User.store' ;
4357
4458export function configureRedux() {
4559 const rootReducer = combineReducers ({
4660 // register our reducers
4761 user: UserStore .reducer ,
4862 });
63+
64+ // infer type from reducers registration
65+ type ConfiguredState = ReturnType <typeof rootReducer >;
66+ type InferredState = {
67+ [storeName in keyof ConfiguredState as storeName extends
68+ | ' [unknown]'
69+ | symbol
70+ | number
71+ ? never
72+ : storeName ]: ConfiguredState [storeName ];
73+ };
4974
5075 const middleware = controllerMiddleware <ReturnType <typeof rootReducer >>({
5176 // use cheap-di container for Dependency Injection
@@ -59,31 +84,30 @@ export function configureRedux() {
5984 getDefaultMiddleware ().concat ([middleware ]),
6085 });
6186
62- return store ;
87+ return {
88+ store ,
89+ // only for convenient type inference
90+ stateType: null as unknown as InferredState ,
91+ };
6392}
64- ```
65-
66- ``` ts
67- // State.ts
68- import { configureRedux } from " ./configureRedux" ;
6993
70- // infer type from reducers registration
71- export type State = ReturnType < ReturnType < typeof configureRedux >[ " getState " ]> ;
94+ type ConfiguredRedux = ReturnType < typeof configureRedux >;
95+ export type State = ConfiguredRedux [ ' stateType ' ] ;
7296```
7397
7498Create a controller to encapsulate a piece of application logic.
7599``` ts
76100// User.controller.ts
77- import { ControllerBase , createAction , watch } from ' @tomas_light/react- redux-controller' ;
78- import { State } from " ./State " ;
79- import { UsersStore } from " ./Users .store" ;
101+ import { ControllerBase , createAction , watch } from ' redux-controller-middleware ' ;
102+ import { State } from ' ./configureRedux ' ;
103+ import { UserStore } from ' ./User .store' ;
80104
81105// prepare the class to use static methods for creating of actions
82106@watch
83107export class UserController extends ControllerBase <State > {
84- // just to shorcat store update calls
85- private updateStore(partialStore : Partial <UsersStore >) {
86- this .dispatch (createAction (UsersStore .update , partialStore ));
108+ // just to shortcat store update calls
109+ private updateStore(partialStore : Partial <UserStore >) {
110+ this .dispatch (createAction (UserStore .update , partialStore ));
87111 }
88112
89113 // add action creator with name of the method: { type: 'loadUserList' }
@@ -116,17 +140,16 @@ export class UserController extends ControllerBase<State> {
116140}
117141
118142// there are restrictions from decorators in TS - it cannot to change type of the decorated class,
119- // so we should manually do it =(
120- // WatchedController takes all methods of the class and adds type definition for static action creators
121- const typedController = ( UserController as unknown ) as WatchedController <UserController >;
122- export { typedController as UserController };
143+ // so we should manually cast types
144+ // WatchedController takes all public methods of the class and adds type definition for static action creators
145+ const userController = UserController as unknown as WatchedController <UserController >;
146+ export { userController as UserController };
123147```
124148
125149And now you can dispatch the controller actions from a component.
126150``` tsx
127- // UserList.ts
128151import { useDispatch } from ' react-redux' ;
129- import { UserController } from ' ./UserController ' ;
152+ import { UserController } from ' ./User.controller ' ;
130153
131154const UserList = () => {
132155 const dispatch = useDispatch ();
@@ -144,14 +167,14 @@ const UserList = () => {
144167That's it!
145168
146169
147- #### <a name =" usage " ></a > Custom action names
170+ ### <a name =" custom-action-names " ></a > Custom action names
148171
149172You can use custom action name, like ` @watch('myCustomActionName') ` .
150173In this case you should to pass name mapper type as second argument of ` WatchedController `
151174
152175``` ts
153- import { ControllerBase , watch , WatchedController } from ' @tomas_light/react- redux-controller' ;
154- import { State } from " ./State " ;
176+ import { ControllerBase , watch , WatchedController } from ' redux-controller-middleware ' ;
177+ import { State } from ' ./configureRedux ' ;
155178
156179@watch
157180export class UserController extends ControllerBase <State > {
@@ -161,17 +184,16 @@ export class UserController extends ControllerBase<State> {
161184 @watch (' loadChatById' ) loadChatByIdFromSpecialStorage(action : Action <{ chatId: string }>) {/* ... */ }
162185}
163186
164- const typedController = ( UserController as unknown ) as WatchedController <UserController , {
187+ const userController = UserController as unknown as WatchedController <UserController , {
165188 loadChatByIdFromSpecialStorage: ' loadChatById' , // map original method name to the new one
166189}>;
167- export { typedController as UserController };
190+ export { userController as UserController };
168191```
169192
170- ### <a name =" controller-di " ></a > Register dependencies
193+ ### <a name =" dependency-injection " ></a > Dependency injection
171194
172195``` ts
173196// api.ts
174-
175197const metadata = <T >(constructor : T ): T => constructor ;
176198
177199export class UserApi {
@@ -184,7 +206,9 @@ export abstract class AccessKey {
184206 abstract key: string ;
185207}
186208
187- @metadata // need any decorator (read cheap-di docs)
209+ // need any decorator for adding metadata to the constructor
210+ // https://github.com/tomas-light/cheap-di#typescript
211+ @metadata
188212export class UserStorage {
189213 constructor (private readonly accessKey : AccessKey ) {}
190214
@@ -194,11 +218,10 @@ export class UserStorage {
194218}
195219```
196220
197-
198221``` ts
199222// App.tsx
200- import { useEffect } from ' react' ;
201223import { container } from ' cheap-di' ;
224+ import { useEffect } from ' react' ;
202225import { AccessKey } from ' ./api' ;
203226
204227const App = () => {
@@ -212,9 +235,9 @@ const App = () => {
212235
213236``` ts
214237// User.controller.ts
215- import { ControllerBase , watch } from ' @tomas_light/react-redux-controller' ;
216- import { State } from " ./State" ;
238+ import { ControllerBase , Middleware , watch } from ' redux-controller-middleware' ;
217239import { UserApi , UserStorage } from ' ./api' ;
240+ import { State } from ' ./configureRedux' ;
218241
219242@watch
220243export class UserController extends ControllerBase <State > {
@@ -234,8 +257,55 @@ export class UserController extends ControllerBase<State> {
234257 }
235258}
236259
237- const typedController = (UserController as unknown ) as WatchedController <UserController >;
238- export { typedController as UserController };
260+ const userController = UserController as unknown as WatchedController <UserController >;
261+ export { userController as UserController };
262+ ```
263+
264+ ### <a name =" create-action " ></a > createAction
265+
266+ You can define action creators by yourself;
267+ ``` ts
268+ import { createAction } from ' redux-controller-middleware' ;
269+
270+ export class UsersActions {
271+ static LOAD_USER_LIST = ' LOAD_USER_LIST' ;
272+ static loadUserList = () => createAction (UsersActions .LOAD_USER_LIST );
273+
274+ static LOAD_USER = ' LOAD_USER' ;
275+ static loadUser = (data : { userID: string }) => createAction (UsersActions .LOAD_USER , data );
276+ }
277+ ```
278+
279+ ### <a name =" chaining-action " ></a > Chaining actions
280+
281+ You can chain action one by one:
282+
283+ ``` tsx
284+ import { useDispatch } from ' react-redux' ;
285+ import { chainActions } from ' redux-controller-middleware' ;
286+ import { UserController } from ' ./User.controller' ;
287+
288+ const UserList = () => {
289+ const dispatch = useDispatch ();
290+
291+ useEffect (() => {
292+ const action = chainActions (
293+ UserController .loadProfile ({ userID: ' 123' }),
294+ UserController .openUserPage ({ userID: ' 123' }),
295+ // ... any other
296+ );
297+ // same as
298+ const action = UserController .loadProfile ({ userID: ' 123' });
299+ action .addNextActions (
300+ UserController .openUserPage ({ userID: ' 123' }),
301+ // ... any other
302+ );
303+
304+ dispatch (action );
305+ }, []);
306+
307+ return <>...</>;
308+ };
239309```
240310
241311### <a name =" without-decorators " ></a > Without decorators
@@ -244,7 +314,7 @@ If you can't use decorators, you have to add watcher to your controller.
244314
245315``` ts
246316// User.watcher.ts
247- import { watcher } from ' @tomas_light/react- redux-controller' ;
317+ import { watcher } from ' redux-controller-middleware ' ;
248318import { UserActions } from ' ./User.actions' ;
249319import { UserController } from ' ./User.controller' ;
250320
@@ -258,7 +328,7 @@ export const UserWatcher = watcher(UserController,
258328
259329``` ts
260330// controllerWatchers.ts
261- import { Watcher } from ' @tomas_light/react- redux-controller' ;
331+ import { Watcher } from ' redux-controller-middleware ' ;
262332import { UserWatcher } from ' /User.watcher' ;
263333
264334const controllerWatchers: Watcher [] = [
@@ -270,9 +340,9 @@ export { controllerWatchers };
270340```
271341``` ts
272342// configRedux.ts
273- import { combineReducers } from " redux" ;
274- import { controllerMiddleware } from " @tomas_light/react- redux-controller" ;
275- import { controllerWatchers } from " ./controllerWatchers" ;
343+ import { combineReducers } from ' redux' ;
344+ import { controllerMiddleware } from ' redux-controller-middleware ' ;
345+ import { controllerWatchers } from ' ./controllerWatchers' ;
276346
277347export function configureRedux() {
278348 const rootReducer = combineReducers (/* ...*/ );
@@ -287,17 +357,3 @@ export function configureRedux() {
287357 return store ;
288358}
289359```
290-
291- ### <a name =" action-creating " ></a > Action creating
292- You can define action creators by yourself;
293- ``` ts
294- import { createAction } from " @tomas_light/react-redux-controller" ;
295-
296- export class UsersActions {
297- static LOAD_USER_LIST = ' LOAD_USER_LIST' ;
298- static loadUserList = () => createAction (UsersActions .LOAD_USER_LIST );
299-
300- static LOAD_USER = ' LOAD_USER' ;
301- static loadUser = (data : { userID: string }) => createAction (UsersActions .LOAD_USER , data );
302- }
303- ```
0 commit comments