1.0.6 • Published 5 years ago

react-redux-patch v1.0.6

Weekly downloads
20
License
MIT
Repository
github
Last release
5 years ago

react-redux-patch

NPM JavaScript Style Guide

What is react-redux-patch?

react-redux-patch is just another library which tries to simplify the web application development for react applications. From various ideas I came across past few years and a few implementations which I think are founding ground for this project, I decided to write this library to abstract the general setup of react-redux application. It specifically focuses and solves the problem of registering dynamic reducers. It also provides shorthand syntax for connect, mapStateToProps and mapDispatchToProps

Install

npm install --save react-redux-patch
yarn add react-redux-patch

Usage

Creating a module

index.js

import { MicroModule } from 'react-redux-patch';
import AppContainer from '@AppContainer/index.js';
import routes from '@routes/index.js';
import reducers from '@reducers/index.js';

const microModule = new MicroModule('TheCoolKids', routes, reducers, AppContainer, 'root');

if (module.hot) {
  module.hot.accept("@routes/index", () => {
    microModule._render("@routes/index");
  })
}

Creating App Container

@AppContainer/index.js

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

export default class AppContainer extends Component {
    static propTypes = {
        cleanRoutes: PropTypes.array.isRequired,
        history: PropTypes.object.isRequired,
        children: PropTypes.oneOfType([
            PropTypes.arrayOf(PropTypes.node),
            PropTypes.node
        ]).isRequired
    }

    render() {
        return (
            <div>
                {children}
            </div>
        );
    }
}

Creating Root Reducers

@reducers/index.js

const common = (state = {}, action) => {
    switch (action.type) {
      default:
        return state;
    }
};

export default {
  common
}

Creating Routes

@routes/index.js

import DashboardPage from '@pages/dashboard/index.js'

const routes = [{
    path: '/',
    key: 'root',
    component: Dashboard,
    children: [{
      path: '/dashboard',
      key: 'dashboard',
      component: DashboardPage
    }]
}]

export default routes;

StatefulComponent: connect, mapStateToProps and mapDispatchToProps Abstraction

@pages/dashboard/index.js

import React, { Component } from "react";
import PropTypes from "prop-types";

const Actions = {
    initialize: () => dispatch => {
        dispatch({ type: "DASHBOARD/INITIALIZE" });
    }
};

const INITIAL_STATE = {
    name: "A sample Dashboard Page"
    productName: "Macbook Pro",
    discount: 0,
    matches: []
};

const Reducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {
        default:
          return state;
    }
}


class DashboardPage extends Component {
  static propTypes = {
    /** Map prop with same name as 
      *  propName i.e. state.dashboard.name 
      **/
    @Prop
    name: PropTypes.string
    
    /** Map Prop with differnt name in state
      * i.e. state.dashboard.product
    @PropMap("productName")
    itemName: PropTypes.string
    
    /** Map prop with same name as propName
      * but update its initial value in store
      * in this case state.dashboard.discount will be mapped.
      * Before mapping it will update passed value in state.
      **/
    @PropInit
    discount: PropTypes.number
    
    /** Map prop with different name in state
      * but update its initial value in store
      * in this case state.dashboard.matches will be mapped.
      * Before mapping it will update passed value in state
      **/
    @PropMapInit("interests")
    matches: PropTypes.array
    
    
    /** Map prop with absolute given key path in state 
      * i.e. state outside of this component
      * Its usage is to access the global state objects that created in root reducers.
      * In this case state.application.name will be mapped.
      **/  
    @PropMapGlobal("state.application.name")
    applicationName: PropTypes.string
    
    /** All actions from Actions object will be available here!!
      * no need to map them
      **/
     initialize: PropTypes.func.isRequired 
  }
  
  render() {
    return (
      <>
        <div>Sample Dashboard Page</div>
        <pre>{JSON.stringify(this.props)}</pre>
      </>
    );
  }
}

/** Returns a connected component 
  * and dynamically attaches reducer at state."dashboard" key 
  ** /
export default StatefulComponent(DashboardPage, Actions, Reducer, "dashboard", false)

WithSaga: Dynamically register saga in application

import React, { Component } from "react";
import { put, takeEvery, all } from 'redux-saga/effects'
import { WithSaga } from "react-redux-patch";

const delay = (ms) => new Promise(res => setTimeout(res, ms))

function* incrementAsync() {
  yield delay(1000)
  yield put({ type: 'INCREMENT' })
}

function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}

// notice how we now only export the rootSaga
// single entry point to start all Sagas at once
function* rootSaga() {
  yield all([
    watchIncrementAsync()
  ])
}

Class ABC extends Component {
  render() {
    return (
      <div>Sample Component</div>
    );
  }
}

WithSaga(ABC, rootSaga);

StatefulComponent: connect, mapStateToProps and mapDispatchToProps Abstraction

Without Decorator Support

Method 1: mapPropTypes - If PropTypes are being used

import React, { Component } from "react";
import PropTypes from "prop-types";
import { mapPropTypes, StatefulComponent, Prop, PropInit, PropMap, PropMapInit, PropMapGlobal } from "react-redux-patch";

const Actions = {
    initialize: () => dispatch => {
        dispatch({ type: "DASHBOARD/INITIALIZE" });
    }
};

const INITIAL_STATE = {
    name: "A sample Dashboard Page"
    productName: "Macbook Pro",
    discount: 0,
    matches: []
};

const Reducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {
        default:
          return state;
    }
}


class DashboardPage extends Component {
  static propTypes = {
    name: PropTypes.string
    itemName: PropTypes.string
    discount: PropTypes.number
    matches: PropTypes.array
    applicationName: PropTypes.string
    
    /** All actions from Actions object will be available here!!
      * no need to map them
      **/
     initialize: PropTypes.func.isRequired 
  }
  
  render() {
    return (
      <>
        <div>Sample Dashboard Page</div>
        <pre>{JSON.stringify(this.props)}</pre>
      </>
    );
  }
}

mapPropTypes(DashboardPage, {
    name: [Prop],
    itemName: [PropMap, "productName"]
    discount: [PropInit],
    matches: [PropMapInit,"interests"],    
    applicationName: [PropMapGlobal,"state.application.name"]
});

/** Returns a connected component 
  * and dynamically attaches reducer at state."dashboard" key 
  ** /
export default StatefulComponent(DashboardPage, Actions, Reducer, "dashboard", false)

Method 2: mapStateProps - If PropTypes are optional or you like to use some enhancers like selectors

import React, { Component } from "react";
import PropTypes from "prop-types";
import { mapStateProps, StatefulComponent, Prop, PropInit, PropMap, PropMapInit, PropMapGlobal } from "react-redux-patch";

const Actions = {
    initialize: () => dispatch => {
        dispatch({ type: "DASHBOARD/INITIALIZE" });
    }
};

const INITIAL_STATE = {
    name: "A sample Dashboard Page"
    productName: "Macbook Pro",
    discount: 0,
    matches: []
};

const Reducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {
        default:
          return state;
    }
}


class DashboardPage extends Component {
  static propTypes = {
    name: PropTypes.string
    itemName: PropTypes.string
    discount: PropTypes.number
    matches: PropTypes.array
    applicationName: PropTypes.string
    
    /** All actions from Actions object will be available here!!
      * no need to map them
      **/
     initialize: PropTypes.func.isRequired 
  }
  
  render() {
    return (
      <>
        <div>Sample Dashboard Page</div>
        <pre>{JSON.stringify(this.props)}</pre>
      </>
    );
  }
}

mapStateProps(DashboardPage, (state, ownProps) => {
    name: state.dashboard.name,
    itemName: state.dashboard.productName,
    discount: state.dashboard.discount,
    matches: state.dashboard.interests,    
    applicationName: state.application.name
});

/** Returns a connected component 
  * and dynamically attaches reducer at state."dashboard" key 
  ** /
export default StatefulComponent(DashboardPage, Actions, Reducer, "dashboard", false)

API

MicroModule(name, routes, reducers, appContainer, containerId , userSpecifiedMiddleWares)

Argument NameSample valueDescriptionOptional
name"Slack"Name of moduleNo
routes`const routes = [{`Routes for applicationNo
path: '/',
key: 'root',
component: Dashboard,
children: [{
path: '/dashboard',
key: 'dashboard',
component: DashboardPage
}]
}]
reducersconst rootReducers = { };Global root reducers if you want to abstract stuff like notificationsNo
appContainerconst appContainer=({props,cleanRoutes}) => (<>{this.props.children</>)App Container where your routes will render. You can customise things like custom header before routes.No
containerIdrootDom element id in which app will be renderYes. Defaults to root
userSpecifiedMiddleWaresAdditional redux middlewares that you need addedYes. Defaults to `[]

StatefulComponent(ReactComponent, Actions, Reducer, DynamicStateName, Inherit?)

Argument NameDefault valueDescriptionOptional
ReactComponent(<></>)A react component which needs to be connectedNo
Actions{}Actions object that will be used in dynamically created mapDispatchToPropsNo
Reducer(state) => { return state }Reducer that needs to be dynamically registered against DynamicStateNameNo
DynamicStateName'\_' + Math.random().toString(36).substr(2, 9)A dynamic state key where reducer will be attachedNo
InheritfalseWhether to create the state dynamically inside another state key generated by StatefulComponentYes

WithSaga(ReactComponent, RootSagaFunc)

Argument NameDescriptionOptional
ReactComponentAny base component before which saga is requiredNo
RootSagaFuncA root saga function to be dynamically registeredNo

License

MIT © saurabhnemade

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago