Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

app-redux-utils

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

app-redux-utils - npm Package Compare versions

Comparing version 1.7.0 to 1.7.1

16

dist/controller/ControllerBase.js
"use strict";
var ControllerBase_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControllerBase = void 0;
class ControllerBase {
const tslib_1 = require("tslib");
const Middleware_1 = require("../Middleware");
// simple decorator allows Reflect-metadata to scan classes ang get its metadata
// it is needed to auto resolve Middleware dependency
const metadata = (constructor) => constructor;
let ControllerBase = ControllerBase_1 = class ControllerBase {
constructor(middleware) {
if (new.target === ControllerBase) {
if (new.target === ControllerBase_1) {
throw new Error('Cannot construct ControllerBase instance directly');

@@ -12,4 +18,8 @@ }

}
}
};
ControllerBase = ControllerBase_1 = tslib_1.__decorate([
metadata,
tslib_1.__metadata("design:paramtypes", [Middleware_1.Middleware])
], ControllerBase);
exports.ControllerBase = ControllerBase;
//# sourceMappingURL=ControllerBase.js.map

1

dist/index.d.ts

@@ -5,3 +5,2 @@ export * from './Middleware';

export * from './createActionWithCallback';
export * from './createReducers';
export { Action, ActionWithCallback, CallbackAction, isAction, DecoratedWatchedController, WatchedController, } from './types';

@@ -8,0 +7,0 @@ export * from './Reducer';

@@ -9,3 +9,2 @@ "use strict";

tslib_1.__exportStar(require("./createActionWithCallback"), exports);
tslib_1.__exportStar(require("./createReducers"), exports);
var types_1 = require("./types");

@@ -12,0 +11,0 @@ Object.defineProperty(exports, "isAction", { enumerable: true, get: function () { return types_1.isAction; } });

{
"name": "app-redux-utils",
"version": "1.7.0",
"version": "1.7.1",
"description": "Helpful utils for redux",

@@ -5,0 +5,0 @@ "contributors": [

* [install](#Installation)
* [usage](#How to use)
* [usage](#How to use controllers)
* [controllers-di](#With cheap-di)
* [reduce-boilerplate](#Reduce boilerplate)
* [controllers](#Using controllers)
* [controllers-di](#Using controllers with cheap-di)
* [reduce-boilerplate](#Reduce boilerplate)
* [redux-saga](#Using with redux-saga)

@@ -11,82 +11,58 @@

```bush
```bash
npm install app-redux-utils
```
### <a name="usage"></a> How to use
### <a name="usage"></a> How to use controllers
Controller - is place for piece of logic in your application.
The differences from Saga (in `redux-saga`) is your methods is not static!
It allows you to use dependency injection technics and simplify tests in some cases.
Create your store
```ts
// User.store.ts
import { User } from './User';
import { Reducer } from 'app-redux-utils';
export class UserStore {
users: User[];
usersAreLoading: boolean;
}
```
class UserStore {
users: string[] = [];
usersAreLoading = false;
```ts
// Users.actions.ts
import { createAction, createActionWithCallback } from "app-redux-utils";
import { UsersStore } from "./Users.store";
export interface LoadUserData {
userId: number;
static update = 'USER_update_store';
static reducer = Reducer(new UserStore(), UserStore.update);
}
export class UsersActions {
static readonly PREFIX = "USERS_";
static readonly UPDATE_STORE = `${UsersActions.PREFIX}UPDATE_STORE`;
static readonly LOAD_USER_LIST = `${UsersActions.PREFIX}LOAD_USER_LIST`;
static readonly LOAD_USER = `${UsersActions.PREFIX}LOAD_USER`;
static readonly LOAD_CURRENT_USER = `${UsersActions.PREFIX}LOAD_CURRENT_USER`;
static readonly LOAD_SOMETHING_ELSE = `${UsersActions.PREFIX}LOAD_SOMETHING_ELSE`;
static updateStore = (partialStore: Partial<UsersStore>) =>
createAction(UsersActions.UPDATE_STORE, partialStore);
static loadUserList = () => createAction(UsersActions.LOAD_USER_LIST);
static loadUser = (data: LoadUserData) => createAction(UsersActions.LOAD_USER, data);
static loadCurrentUser = () => createActionWithCallback(UsersActions.LOAD_CURRENT_USER);
static loadSomethingElse = () => createAction(UsersActions.LOAD_SOMETHING_ELSE);
}
```
Register store reducer and add app-redux-utils middleware to redux.
You can also register DI container, that allows you to inject services in controllers.
```ts
// UsersPageContainer.ts
import { ComponentType } from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
// configRedux.ts
import { combineReducers } from "redux";
import { configureStore } from "@reduxjs/toolkit";
import { UsersActions } from "./Users.actions";
import { Props, UsersPage } from "./UsersPage";
import { controllerMiddleware } from "app-redux-utils";
import { container } from "cheap-di";
const mapDispatchToProps = (dispatch: Dispatch): Props => ({
loadUserList: () => dispatch(UsersActions.loadUserList()),
loadUser: (userId: number) => dispatch(UsersActions.loadUser({ userId })),
loadCurrentUser: () => dispatch(
UsersActions.loadCurrentUser()(
// this action will be dispatched after loadCurrentUser will be handled in controller
() => UsersActions.loadSomethingElse()
)
),
});
import { UserStore } from "./User.store";
const UsersPageContainer: ComponentType = connect(
null,
mapDispatchToProps
)(UsersPage);
export function configureRedux() {
const rootReducer = combineReducers({
// register our reducers
user: UserStore.reducer,
});
export { UsersPageContainer };
```
const middleware = controllerMiddleware<ReturnType<typeof rootReducer>>({
// add cheap-di container to as DI resolver
container,
});
```ts
// Users.reducer.ts
import { Reducer } from "app-redux-utils";
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
// add our middleware to redux
getDefaultMiddleware().concat([middleware]),
});
import { UsersActions } from "./Users.actions";
import { UsersStore } from "./Users.store";
export const UsersReducer = Reducer(new UsersStore(), UsersActions.UPDATE_STORE);
return store;
}
```

@@ -96,56 +72,12 @@

// State.ts
import { UsersStore } from "./Users.store";
import { SomeStore } from "./Some.store";
import { configureRedux } from "./configureRedux";
export class State {
public usersStore: UsersStore;
public someStore: SomeStore;
}
// infer type from reducers registration
export type State = ReturnType<ReturnType<typeof configureRedux>["getState"]>;
```
Create a controller to encapsulate a piece of application business logic.
```ts
// getReducers.ts
import { ReducersMapObject } from "redux";
import { State } from "./State";
import { UsersReducer } from "./Users.reducer";
import { SomeReducer } from "./Some.reducer";
export function getReducers(): ReducersMapObject<State, any> {
return {
usersStore: UsersReducer,
someStore: SomeReducer,
};
}
```
```ts
// configureApp.ts
import { createReducers } from "app-redux-utils";
import { applyMiddleware, compose, createStore, Store } from "redux";
import { State } from "./State";
import { getReducers } from "./getReducers";
export function configureApp(): Store<State> {
const devtoolsComposer = window["__REDUX_DEVTOOLS_EXTENSION_COMPOSE__"];
const composeEnhancer = devtoolsComposer || compose;
const store: Store<State> = createStore(
createReducers(getReducers),
composeEnhancer(applyMiddleware())
);
return store;
}
```
### <a name="controllers"></a> Using controllers
Controller - is place for piece of logic in your application.
It differences from Saga in redux-saga - your methods is not static!
It allows you to use dependency injection technics and simplify tests in some cases.
```ts
// User.controller.ts
import { ControllerBase } from 'app-redux-utils';
import { ControllerBase, watch } from 'app-redux-utils';
import { State } from "./State";

@@ -155,3 +87,5 @@ import { UsersActions } from "./Users.actions";

@watch // this decorator adds action creators as static members of class
export class UserController extends ControllerBase<State> {
// just to shorcat store update calls
private updateStore(partialStore: Partial<UsersStore>) {

@@ -161,2 +95,3 @@ this.dispatch(UsersActions.updateStore(partialStore));

@watch // add register action creator with name of method
async loadUserList() {

@@ -185,11 +120,120 @@ this.updateStore({

loadUser(action: Action<LoadUserData>) {/* ... */}
loadCurrentUser() {/* ... */}
loadSomethingElse() {/* ... */}
@watch loadUser(action: Action<{ userID: string }>) {/*...*/}
@watch loadCurrentUser() {/*...*/}
@watch loadSomethingElse() {/*...*/}
}
const typedController = (UserController as unknown) as WatchedController<UserController>;
export { typedController as UserController };
```
And now you can dispatch the controller actions from a component.
```tsx
// UserList.ts
import { useDispatch } from 'react-redux';
import { UserController } from './UserController';
const UserList = () => {
const dispatch = useDispatch();
useEffect(() => {
// create action and dispatch it in one line
dispatch(UserController.loadUsers());
dispatch(UserController.loadUser({ userID: '123' }));
}, []);
return <>...</>;
};
```
That's it!
#### <a name="usage"></a> Custom action names
You can use custom action name, like `@watch('myCustomActionName')`.
But in this case you should change define this method with `DecoratedWatchedController`
```ts
import { ControllerBase, watch, DecoratedWatchedController } from 'app-redux-utils';
import { State } from "./State";
@watch
export class UserController extends ControllerBase<State> {
/* ... */
@watch loadUser(action: Action<{ userID: string }>) {/* ... */}
@watch('loadChatInfo') loadCurrentUser(action: Action<{ chat: boolean }>) {/* ... */}
}
type Controller =
// keep infered type for all actions except action with custom action type
Omit<WatchedController<UserController>, 'loadCurrentUser'>
// specify type for custom action
& DecoratedWatchedController<[
['loadChatInfo', { userID: string; }]
]>;
const typedController = (UserController as unknown) as Controller;
export { typedController as UserController };
```
### <a name="controller-di"></a> Register dependencies
```ts
// UserApi.ts
export class UserApi {
loadUsers() {
return fetch('/api/users');
}
}
```
```ts
// App.tsx
import { useEffect } from 'react';
import { container } from 'cheap-di';
import { UserApi } from './UserApi';
const App = () => {
useEffect(() => {
container.registerType(UserApi);
}, []);
return /* your layout */;
}
```
```ts
// User.controller.ts
import { ControllerBase, watch } from 'app-redux-utils';
import { State } from "./State";
import { UsersActions } from "./Users.actions";
import { UsersStore } from "./Users.store";
@watch
export class UserController extends ControllerBase<State> {
constructor(middleware: Middleware<State>, private userApi: UserApi) {
super(middleware);
}
@watch
async loadUserList() {
const response = await this.userApi.loadUsers();
/*...*/
}
}
const typedController = (UserController as unknown) as WatchedController<UserController>;
export { typedController as UserController };
```
### <a name="usage"></a> Without decorators
If you can't use decorators, you have to add watcher to your controller.
```ts
// User.watcher.ts
import { watcher } from 'app-saga-utils';
import { watcher } from 'app-redux-utils';

@@ -216,3 +260,3 @@ import { UserActions } from './User.actions';

// controllerWatchers.ts
import { Watcher } from 'app-saga-utilsr';
import { Watcher } from 'app-redux-utils';
import { State } from "./State";

@@ -228,18 +272,19 @@ import { UserWatcher } from '/User.watcher';

```
```ts
// configureApp.ts
// configRedux.ts
import { combineReducers } from "redux";
import { controllerMiddleware } from "app-redux-utils";
import { createStore, /* ... */ } from "redux";
// ...
import { controllerWatchers } from "./controllerWatchers";
// ...
export function configureApp(): Store<State> {
export function configureRedux() {
const rootReducer = combineReducers(/*...*/);
const middleware = controllerMiddleware<ReturnType<typeof rootReducer>>({
container,
watchers: controllerWatchers,
});
// ...
const store: Store<State> = createStore(
// ...
controllerMiddleware(controllerWatchers) // here we connect middleware
);
return store;

@@ -249,114 +294,55 @@ }

### <a name="controller-di"></a> Using controllers with cheap-di
```ts
// configureApp.ts
import { controllerMiddleware } from "app-redux-utils";
import { container } from 'cheap-di';
import { createStore, /* ... */ } from "redux";
import { controllerWatchers } from "./controllerWatchers";
// ...
export function configureApp(): Store<State> {
// ...
const store: Store<State> = createStore(
// ...
controllerMiddleware(controllerWatchers, container) // add container as second param
);
return store;
}
```
### <a name="controller-di"></a> Manual action creating
You can define action creators by yourself;
```ts
// User.controller.ts
import { ControllerBase, AbstractStore } from 'app-redux-utils';
import { State } from "./State";
import { UsersActions } from "./Users.actions";
// Users.actions.ts
import { createAction, createActionWithCallback } from "app-redux-utils";
import { UsersStore } from "./Users.store";
import { metadata } from "./metadata"; // read cheap-di README to know what is it
@metadata
export class UserController extends ControllerBase<State> {
constructor(
store: AbstractStore<State>,
private readonly service: MyService
) {
super(store);
}
// ...
}
```
export class UsersActions {
static readonly PREFIX = "USERS_";
```ts
// App.tsx
import { useEffect } from 'react';
import { container } from 'cheap-di';
static readonly LOAD_USER_LIST = `${UsersActions.PREFIX}LOAD_USER_LIST`;
static readonly LOAD_USER = `${UsersActions.PREFIX}LOAD_USER`;
static readonly LOAD_CURRENT_USER = `${UsersActions.PREFIX}LOAD_CURRENT_USER`;
static readonly LOAD_SOMETHING_ELSE = `${UsersActions.PREFIX}LOAD_SOMETHING_ELSE`;
const App = () => {
useEffect(() => {
container.registerType(MyService);
}, []);
return /* your layout */;
static loadUserList = () => createAction(UsersActions.LOAD_USER_LIST);
static loadUser = (data: { userID: string }) => createAction(UsersActions.LOAD_USER, data);
static loadCurrentUser = () => createActionWithCallback(UsersActions.LOAD_CURRENT_USER);
static loadSomethingElse = () => createAction(UsersActions.LOAD_SOMETHING_ELSE);
}
```
### <a name="reduce-boilerplate"></a> Reduce boilerplate
```ts
// UsersPageContainer.ts
import { ComponentType } from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
You can avoid boilerplate by using decorators
import { UsersActions } from "./Users.actions";
import { Props, UsersPage } from "./UsersPage";
`MyController.ts`
```tsx
import { ControllerBase, DecoratedWatchedController, Reducer, createAction, WatchedController } from 'app-redux-utils';
import { useDispatch } from 'react-redux';
const mapDispatchToProps = (dispatch: Dispatch): Props => ({
loadUsers: () => dispatch(UsersActions.loadUserList()),
loadUser: (userID: string) => dispatch(UsersActions.loadUser({ userID })),
loadCurrentUser: () => dispatch(
UsersActions.loadCurrentUser()(
// this action will be dispatched after loadCurrentUser will be handled in controller
() => UsersActions.loadSomethingElse()
)
),
});
type State = {
my: MyStore;
};
const UsersPageContainer: ComponentType = connect(
null,
mapDispatchToProps
)(UsersPage);
class MyStore {
users: string[] = [];
static update = 'My_update_store';
static reducer = Reducer(new MyStore(), MyStore.update);
}
@watch
class MyController extends ControllerBase<State> {
updateStore(store: Partial<MyStore>) {
this.dispatch(createAction(MyStore.update, store));
}
@watch('openUserForEditing')
openUser(action: Action<{ userID: string; }>) {
//...
}
}
const myController: DecoratedWatchedController<[
'loadUsers' |
['openUserForEditing', { userID: string; }]
]> = MyController as any;
// if you use the same name for all Controller methods (like `@watch addUser(action: ...) {...}`)
// const myController: WatchedController<MyController> = MyController as any;
export { myController as MyController };
export { UsersPageContainer };
```
```tsx
import { useDispatch } from 'react-redux';
import { MyController } from './MyController';
const MyComponent = () => {
const dispatch = useDispatch();
dispatch(MyController.loadUsers());
dispatch(MyController.openUserForEditing({ userID: '123' }));
return <>beatiful button</>;
};
```

@@ -366,11 +352,8 @@ ### <a name="redux-saga"></a> Using with redux-saga

```ts
// Users.saga.ts
// User.saga.ts
import { Action } from "app-redux-utils";
import { LoadUserData } from "../redux/Users.actions";
export class UsersSaga {
static * loadUserList() {/* ... */}
static * loadUser(action: Action<LoadUserData>) {/* ... */}
static * loadCurrentUser() {/* ... */}
static * loadSomethingElse() {/* ... */}
static * loadUsers() {/* ... */}
static * loadUser(action: Action<{ userID: string }>) {/* ... */}
}

@@ -380,4 +363,3 @@ ```

```ts
// Users.watcher.ts
// User.watcher.ts
import { SagaMiddleware } from "redux-saga";

@@ -387,8 +369,8 @@ import { ForkEffect, put, PutEffect, TakeEffect, takeLatest } from "@redux-saga/core/effects";

import { UsersActions } from "../redux/Users.actions";
import { UsersSaga } from "./Users.saga";
import { UserActions } from "../redux/User.actions";
import { UserSaga } from "./User.saga";
type WatchFunction = () => IterableIterator<ForkEffect | TakeEffect | PutEffect>;
export class UsersWatcher {
export class UserWatcher {
public watchFunctions: WatchFunction[];

@@ -400,13 +382,9 @@

this.watchLatest(
UsersActions.LOAD_USERS,
UsersSaga.loadUsers
UserActions.LOAD_USERS,
UserSaga.loadUsers
);
this.watchLatest(
UsersActions.LOAD_USER,
UsersSaga.loadUser
UserActions.LOAD_USER,
UserSaga.loadUser
);
this.watchLatest(
UsersActions.LOAD_CURRENT_USER,
UsersSaga.loadCurrentUser
);
}

@@ -442,24 +420,12 @@

```ts
// configureApp.ts
import { createReducers } from "app-redux-utils";
import { applyMiddleware, createStore /* ... */ } from "redux";
import createSagaMiddleware, { SagaMiddleware } from "redux-saga";
import { State } from "./State";
import { getReducers } from "./getReducers";
import { UsersWatcher } from "./Users.watcher";
export function configureApp(): Store<State> {
// configureRedux.ts
export function configureRedux() {
// ...
const sagaMiddleware = /* create middleware*/;
const store: Store<State> = createStore(
createReducers(getReducers),
composeEnhancer(applyMiddleware(createSagaMiddleware()))
);
const watcher = new UsersWatcher();
const watcher = new UserWatcher();
watcher.run(sagaMiddleware);
return store;
// ...
}
```

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc