KarlPurk/redux-decorators

View on GitHub
src/reducer.decorator.ts

Summary

Maintainability
A
0 mins
Test Coverage
import {Reducer} from 'redux';

let rootReducer: Reducer;
let initialState: any = {};

//------------------------------------------------------------------------------
// Initial state
//------------------------------------------------------------------------------

export function setInitialState(state: any): any {
    initialState = state;
}

//------------------------------------------------------------------------------
// Root reducer
//------------------------------------------------------------------------------

export function setReducer(reducer: Reducer): void {
    rootReducer = reducer;
}

export function getReducer(): Reducer {
    return rootReducer || DefaultReducer.prototype.reducer;
}

export interface RootReducer {
    prototype?: any;// I shouldn't need this
    reducer(state: any, action: any): any;
}

export class DefaultReducer implements RootReducer {
    reducer(state: any = initialState, action: any): any {

        let matchingActionTypeOnly = (actionReducer) => {
            return actionReducer.type === action.type;
        };

        if (action.type === '@@redux/INIT') {
            return actionReducers.reduce((nextState, reducer) => {
                if (!reducer.owner.getInitialState) {
                    return state;
                }
                const initialState = reducer.owner.getInitialState(reducer.type);
                const slice = reducer.owner.getSlice ? reducer.owner.getSlice(reducer.methodName) : null;
                if (initialState !== undefined && slice) {
                    nextState[slice] = initialState;
                }
                return nextState;
            }, initialState);
        }

        let filteredActionReducers = actionReducers.filter(matchingActionTypeOnly);

        if (!filteredActionReducers.length) {
            return state;
        }

        let createNextState = (state, {owner, methodName}) => {
            const reducer = owner[methodName];
            let slice, nextState;
            slice = owner.getSlice ? owner.getSlice(methodName) : null;
            if (slice) {
                const inputState = state.hasOwnProperty(slice) ? state[slice] : undefined;
                nextState = state;
                nextState[slice] = reducer(inputState, ...action.data);
            }
            else {
                nextState = reducer(state, ...action.data);
            }
            return Object.assign(state, nextState);
        }

        return filteredActionReducers.reduce(createNextState, state);
    }
}

//------------------------------------------------------------------------------
// Action reducers
//------------------------------------------------------------------------------

interface ActionReducer {
    type: string;
    owner: any;
    methodName: string;
}

let actionReducers: ActionReducer[] = [];

export function addActionReducer(type: string, owner: Function, methodName: string): void {
    actionReducers.push({type, owner, methodName});
}

export function getActionReducers(): ActionReducer[] {
    return actionReducers;
}

export function removeActionReducers(): void {
    actionReducers = [];
}

//------------------------------------------------------------------------------
// Decorator
//------------------------------------------------------------------------------

let handleActionReducer = function(owner: any, methodName: string): void {
    addActionReducer(methodName, owner, methodName);
}

let handleRootReducer = function(owner: any, methodNames: string[]): void {
    if (owner.prototype.reducer) {
        rootReducer = owner.prototype.reducer;
    }
    let mapMethodNames = (methodName) => { return { type: methodName, owner: owner.prototype, methodName } };
    actionReducers = actionReducers.concat(methodNames.map(mapMethodNames));
}

export function Reducer(...methodNames: string[]): Function {
    return function(owner: any, methodName?: string): void {
        if (!owner.prototype) {
            handleActionReducer(owner, methodName);
            return;
        }
        handleRootReducer(owner, methodNames);
    }
}