react-redux-patch
v1.0.6
Published
>
Downloads
16
Readme
react-redux-patch
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 Name | Sample value| Description | Optional |
|---------------|:-------------|:------------|:---------|
|name|"Slack"
| Name of module | No |
|routes| const routes = [{``` | Routes for application | No |
| |
path: '/', |||
| |
key: 'root', |||
| |
component: Dashboard, |||
| |
children: [{ |||
| |
path: '/dashboard', |||
| |
key: 'dashboard', |||
| |
component: DashboardPage |||
| |
}] |||
| |
}]`|||
|reducers| const rootReducers = { };
| Global root reducers if you want to abstract stuff like notifications | No |
|appContainer| const appContainer=({props,cleanRoutes}) => (<>{this.props.children</>)
| App Container where your routes will render. You can customise things like custom header before routes. | No|
|containerId| root
|Dom element id in which app will be render|Yes. Defaults to root
|
|userSpecifiedMiddleWares||Additional redux middlewares that you need added | Yes. Defaults to ```[]````|
StatefulComponent(ReactComponent, Actions, Reducer, DynamicStateName, Inherit?)
| Argument Name | Default value| Description | Optional |
|---------------|:-------------|:------------|:---------|
| ReactComponent | (<></>) | A react component which needs to be connected | No |
| Actions | {} | Actions object that will be used in dynamically created mapDispatchToProps | No |
| Reducer | (state) => { return state } | Reducer that needs to be dynamically registered against DynamicStateName | No |
| DynamicStateName | '\_' + Math.random().toString(36).substr(2, 9)
| A dynamic state key where reducer will be attached | No |
| Inherit | false | Whether to create the state dynamically inside another state key generated by StatefulComponent | Yes|
WithSaga(ReactComponent, RootSagaFunc)
| Argument Name | Description | Optional | |---------------|-------------|----------| | ReactComponent | Any base component before which saga is required | No| | RootSagaFunc | A root saga function to be dynamically registered | No |
License
MIT © saurabhnemade