Redux-arena is a tool for modularizing redux/redux-saga with react component. Scope redux/redux-saga within bundle, and auto clear redux/redux-saga in bundle after react component unmounted.
Redux is a great state management container, I love redux-saga and react-router very much. But when I am developing a webapp for management console, packaging a dozen url related page-components into one entity is annoying.
Every url related page-component in management console webapp is nearly independent, but when we register its state and reducer in original redux, states and reducers will work all life-circle of redux. That's not necessary, it will cause some problems like same action type conflict, reducer inefficient, etc.
Redux-arena is designed to solve these problems once and for all, when scene changed, the state, actions, reducer, and saga will be replaced by redux-arena, looks like the two scene connected with irrelevant store.
And any issue or pr is welcomed.
npm install redux-arena --save
An complete example is under /example
directory, including a lot of HOC. And add redux-devtools for state changing show.
Online example can be found here: Here
- Bundle react component, actions, reducer, saga, mapStateToProps and export.
redux-arena extends original mapStateToProps, reducerKey is created by redux-arena and referring to current bundle state.
import state from "./state";
import saga from "./saga";
import reducer from "./reducer";
import * as actions from "./actions";
import PageA from "./PageA";
export default {
Component: PageA,
state,
saga,
reducer,
actions,
mapStateToProps: function mapStateToProps(state, reducerKey) {
return {
pageA: state[reducerKey].pageA,
name: state[reducerKey].name,
dynamicState: state[reducerKey].dynamicState,
cnt: state[reducerKey].cnt
};
}
};
- Replace Switch/Route of react-router with ArenaSwitch/RouteScene
import React, { Component } from "react";
import PropTypes from "prop-types";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { BrowserRouter, Link } from "react-router-dom";
import { RouteScene , ArenaSwitch } from "redux-arena";
import pageABundle from "./pageABundle";
import * as actions from "./actions";
const asyncPageB = import("./pageBBundle");
eport default class Frame extends Component {
render() {
return (
<div>
<ul>
<li>
<Link to="/pageA">pageA</Link>
</li>
<li>
<Link to="/asyncPageB">asyncPageB</Link>
</li>
</ul>
<div style={{ marginTop: "1rem" }}>
<BrowserRouter>
<ArenaSwitch>
<RouteScene path="/pageA" sceneBundle={pageABundle} />
<RouteScene path="/asyncPageB" asyncSceneBundle={asyncPageB} />
</ArenaSwitch>
</BrowserRouter>
</div>
</div>
);
}
}
- Initial arenaStore and provide it for redux.
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createArenaStore } from "redux-arena";
import Frame from "./Frame";
import thunk from "redux-thunk";
const store = createArenaStore();
const app = document.getElementById("app");
ReactDOM.render(
<Provider store={store}>
<Frame />
</Provider>,
app,
function() {
document.getElementById("app").className = "";
}
);
Redux-arena provide a set of useful HOCs to enhence react-router.
If route is not necessary, use SoloScene.
<SoloScene asyncSceneBundle={asyncPageB} />
Instead of Switch of react-router.
<ArenaSwitch>
<RouteScene path="/pageA" sceneBundle={pageABundle} />
<PrivateRouteScene path="/asyncPageB" asyncSceneBundle={asyncPageB} />
</ArenaSwitch>
An enhenced Route witch accept sceneBundle prop. Support asyncSceneBundle for code splitting. Prop exact and strict and other props defined in Route of react-router will work.
<RouteScene path="/asyncPageB" asyncSceneBundle={asyncPageB} exact strict />
An enhenced RouteScene whitch accept onValidate onPass and onReject props. Helpful when integrating with access control system.
<PrivateRouteScene path="/asyncPageB"
sceneBundle={pageA}
onValidate={(cb,match,location,type)=>cb(true)}
onPass={(data)=>console.debug("pass")}
onReject={(data)=>this.props.jumpTo("/login")}
/>
redux-arena extends reducer of redux, add incoming reducerKey, comparing to _sceneReducerKey of action and decide updating or not.
// original reducer
function reducer(state = initState, action){
//...
}
// extended reducer
function reducer(state = initState, action, reducerKey){
//...
}
With sceneReducer, reducer will only accept action dispatched from current bundle.
import { sceneReducer } from "redux-arena/sceneScope";
function reducer(state = initState, action, reducerKey){
//...
}
export default sceneReducer(reducer)
With setSceneState and getSceneState, getting and updating bundle state easily.
import { setSceneState, getSceneState } from "redux-arena/sagaOps";
function * doSomthing(){
let { a } = yield* getSceneState()
yield* setSceneState({ a:a+1 })
}
With sceneActionSaga, action saga will only accept action dispatched from current bundle.
import { sceneActionSaga } from "redux-arena/sceneScope";
import { setSceneState } from "redux-arena/sagaOps";
function * doSomthing({ payload }){
yield* setSceneState({ payload })
}
export function* saga (){
yield takeLatest("DO_SOMETHING",sceneActionSaga(doSomthing))
}