Skip to content

Powerful extensitions for building class-based Redux architecture powered by TypeScript.

License

Notifications You must be signed in to change notification settings

shtanko-michael/redux-typed-kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

redux-typed-kit

Powerful extensitions for building class-based Redux architecture powered by TypeScript.

Example of using redux-typed-kit-example

Installation

  1. Run

    npm i redux-typed-kit
    
  2. Configure your Babel to support class properties and decorators inside .babelrc

    "plugins": [
         ["@babel/plugin-proposal-decorators", { "legacy": true }],
         ["@babel/plugin-proposal-class-properties", { "loose": true }]
    ]
  3. If you use TypeScript you should configure it inside tsconfig.json

    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
  4. If you use React you also should install reflect-metadata

    npm i reflect-metadata
    

    and then import it at the top of the entry file

    import 'reflect-metadata';

Quick start

Let's create a simple Redux-architecture to fetch users from server and display them. We'll also take care about possible network (and others) errors.

States

Let's describe users state

import { State } from 'redux-typed-kit';

export default class UsersState extends State {
  public users: Array<User> = [];
  public filter: UsersFilter = new UsersFilter();
  public isLoading: boolean = false;
  public error: ApiError;
}

AppState should include all other states

import { State } from 'redux-typed-kit';

export default class AppState extends State {
  public usersState: UsersState;
}

RebuildState

The State class has the Rebuild method, which is needed to create an instance of the State class, based on the parent instance, but with some changes. It's very helpful to use in any reducers, especially when you need to save the type of your state class.

state.rebuild(x => {
  x.isLoading = true;
});

Actions

Let's create three actions for fetching users. Action to start fetching, action in case of success and in case of error.

import { Action } from 'redux-typed-kit';

export class FetchUsersAction extends Action {
  constructor(public filter: FetchUsersFilter) {
    super();
  }
}

export class FetchUsersSuccessAction extends Action {
  constructor(public response: FetchUsersResponse) {
    super();
  }
}

export class FetchUsersFailureAction extends Action {
  constructor(public error: ApiError) {
    super();
  }
}

Reducers

Let's create users reducer. Every reducer must have two basic things: initial state and name. This name will be used to configure the global app state.

import { Reducer, ActionDecorator as Action } from 'redux-typed-kit';
import UsersState from '../models/states/users-state';
import { FetchUsersAction, FetchUsersSuccessAction, FetchUsersFailureAction } from '../actions/fetch-users-action';

export default class UsersReducer extends Reducer<UsersState> {
  initialState = new UsersState();
  name = 'usersState';

  @Action
  fetchUsers(state: UsersState, action: FetchUsersAction): UsersState {
    return state.rebuild(x => {
      x.isLoading = !action.isBackgroud;
    });
  }

  @Action
  fetchUsersSuccess(state: UsersState, action: FetchUsersSuccessAction): UsersState {
    return state.rebuild(x => {
      x.users = action.response.users;
      x.isLoading = false;
    });
  }

  @Action
  fetchUsersFailure(state: UsersState, action: FetchUsersFailureAction): UsersState {
    return state.rebuild(x => {
      x.error = action.error;
      x.isLoading = false;
    });
  }
}

CombinedReducer

CombinedReducer combines several reducers into one complex reducer and state object just like combineReducers function does.

import { CombinedReducer } from 'redux-typed-kit';
export default new CombinedReducer('complexState', new SimpleReducer(), new AnotherSimpleReducer());

RootReducer

RootReducer combines others reducers (including CombinedReducer) and used by store.

import { RootReducer } from 'redux-typed-kit';
import UsersReducer from './users-reducer';

const rootReducer = new RootReducer(
  new UsersReducer() /*, new CombinedReducer(...), new AnotherReducer()*/
);
export default rootReducer;

Middlewares

We will use middleware to make a request to the server, process the response, and send data to the reducers.

import { PostMiddlewareHandler, Middleware, Store } from 'redux-typed-kit';
import AppState from '../models/states/app-state';
import { FetchUsersAction, FetchUsersSuccessAction, FetchUsersFailureAction, FetchUsersCancelAction } from '../actions/fetch-users-action';

export default class UsersMiddleware extends Middleware<AppState> {
    @PostMiddlewareHandler
    async fetchUsers(store: Store<AppState>, action: FetchUsersAction) {
        const result = await api.fetchUsers(action.filter);
        if (result.error == null) {
            store.dispatch(new FetchUsersSuccessAction(result.response));
        } else {
            store.dispatch(new FetchUsersFailureAction(result.error));
        }
    }
}

PreMiddlewareHandler

PreMiddlewareHandler decorator is used when we need to catch any dispached actions before they get to the reducers. So we can get state from Store before any changes are applied inside the reducers.

PostMiddlewareHandler

PostMiddlewareHandler decorator works exactly the opposite. All reducers already handled the action and changed the state.

Probably the best example I can give you is middlware for logging all actions and state's changes.

import { PostMiddlewareHandler, PreMiddlewareHandler, Middleware, Store } from 'redux-typed-kit';

export default class LoggerMiddleware extends Middleware<AppState> {
    @PreMiddlewareHandler
    preLog(store: Store<AppState>, action: any) {
        console.log('PRE', action, JSON.parse(JSON.stringify(store.getState())));
    }

    @PostMiddlewareHandler
    postLog(store: Store<AppState>, action: any) {
        console.log('POST', action, JSON.parse(JSON.stringify(store.getState())));
    }
}

Store

Store is a combination of RootReducer and Middlewares that create a final state object.

import { Store } from 'vue-redux-ts';
import AppState from '../models/states/app-state';
import rootReducer from '../reducers/root-reducer';
import UsersMiddleware from '../middlewares/users-middleware';

const store = new Store<AppState>(rootReducer, new UsersMiddleware());
export default store.init();

Documentation coming soon...

About

Powerful extensitions for building class-based Redux architecture powered by TypeScript.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published