- install
- [usage](#How to use)
- [controllers](#Using controllers)
- [controllers-di](#Using controllers with cheap-di)
- [redux-saga](#Using with redux-saga)
Installation
npm install app-redux-utils
How to use
import { User } from './User';
export class UserStore {
users: User[];
usersAreLoading: boolean;
}
import { createAction, createActionWithCallback } from "app-redux-utils";
import { UsersStore } from "./Users.store";
export interface LoadUserData {
userId: number;
}
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);
}
import { ComponentType } from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { UsersActions } from "./Users.actions";
import { Props, UsersPage } from "./UsersPage";
const mapDispatchToProps = (dispatch: Dispatch): Props => ({
loadUserList: () => dispatch(UsersActions.loadUserList()),
loadUser: (userId: number) => dispatch(UsersActions.loadUser({ userId })),
loadCurrentUser: () => dispatch(
UsersActions.loadCurrentUser()(
() => UsersActions.loadSomethingElse()
)
),
});
const UsersPageContainer: ComponentType = connect(
null,
mapDispatchToProps
)(UsersPage);
export { UsersPageContainer };
import { Reducer } from "app-redux-utils";
import { UsersActions } from "./Users.actions";
import { UsersStore } from "./Users.store";
export const UsersReducer = Reducer(new UsersStore(), UsersActions.UPDATE_STORE);
import { UsersStore } from "./Users.store";
import { SomeStore } from "./Some.store";
export class State {
public usersStore: UsersStore;
public someStore: SomeStore;
}
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,
};
}
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;
}
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.
import { ControllerBase } from 'app-redux-utils';
import { State } from "./State";
import { UsersActions } from "./Users.actions";
import { UsersStore } from "./Users.store";
export class UserController extends ControllerBase<State> {
private updateStore(partialStore: Partial<UsersStore>) {
this.dispatch(UsersActions.updateStore(partialStore));
}
async loadUserList() {
this.updateStore({
usersAreLoading: true,
});
const response = await fetch('/api/users');
if (!response.ok) {
this.updateStore({
usersAreLoading: false,
});
return;
}
const users = await response.json();
this.updateStore({
usersAreLoading: false,
users,
});
}
loadUser(action: Action<LoadUserData>) {}
loadCurrentUser() {}
loadSomethingElse() {}
}
import { watcher } from 'app-saga-utils';
import { UserActions } from './User.actions';
import { UserController } from './User.controller';
export const UserWatcher = watcher<UserController>(
UserController,
[
[
UserActions.LOAD_USER_LIST,
'loadUserList',
],
[
UserActions.LOAD_USER,
'loadUser',
],
]);
import { Watcher } from 'app-saga-utilsr';
import { State } from "./State";
import { UserWatcher } from '/User.watcher';
const controllerWatchers: Watcher<State>[] = [
UserWatcher,
];
export { controllerWatchers };
import { controllerMiddleware } from "app-redux-utils";
import { createStore, } from "redux";
import { controllerWatchers } from "./controllerWatchers";
export function configureApp(): Store<State> {
const store: Store<State> = createStore(
controllerMiddleware(controllerWatchers)
);
return store;
}
Using controllers with cheap-di
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)
);
return store;
}
import { ControllerBase, AbstractStore } from 'app-redux-utils';
import { State } from "./State";
import { UsersActions } from "./Users.actions";
import { UsersStore } from "./Users.store";
import { metadata } from "./metadata";
@metadata
export class UserController extends ControllerBase<State> {
constructor(
store: AbstractStore<State>,
private readonly service: MyService
) {
super(store);
}
}
import { useEffect } from 'react';
import { container } from 'cheap-di';
const App = () => {
useEffect(() => {
container.registerType(MyService);
}, []);
return ;
}
Using with redux-saga
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() {}
}
import { SagaMiddleware } from "redux-saga";
import { ForkEffect, put, PutEffect, TakeEffect, takeLatest } from "@redux-saga/core/effects";
import { Action } from "app-redux-utils";
import { UsersActions } from "../redux/Users.actions";
import { UsersSaga } from "./Users.saga";
type WatchFunction = () => IterableIterator<ForkEffect | TakeEffect | PutEffect>;
export class UsersWatcher {
public watchFunctions: WatchFunction[];
constructor() {
this.watchFunctions = [];
this.watchLatest(
UsersActions.LOAD_USERS,
UsersSaga.loadUsers
);
this.watchLatest(
UsersActions.LOAD_USER,
UsersSaga.loadUser
);
this.watchLatest(
UsersActions.LOAD_CURRENT_USER,
UsersSaga.loadCurrentUser
);
}
private getSagaWithCallbackAction(saga: (action: Action) => void): (action: Action) => void {
return function* (action: Action) {
yield saga(action);
if (!action.stopPropagation) {
const actions = action.getActions();
const putActionEffects = actions.map(action => put(action()));
yield all(putActionEffects);
}
};
}
private watchLatest(actionType: string, saga: (action: Action) => void) {
const sagaWithCallbackAction = this.getSagaWithCallbackAction(saga);
this.watchFunctions.push(
function* () {
yield takeLatest(actionType, sagaWithCallbackAction);
}
);
}
public run(sagaMiddleware: SagaMiddleware) {
this.watchFunctions.forEach(saga => sagaMiddleware.run(saga));
}
}
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> {
const store: Store<State> = createStore(
createReducers(getReducers),
composeEnhancer(applyMiddleware(createSagaMiddleware()))
);
const watcher = new UsersWatcher();
watcher.run(sagaMiddleware);
return store;
}