Redux React Boilerplate

Long time no articles. Last one year working on html5 apps, hybrid apps, using backbone. Faced some problems with backbone, I thought more about frontend. several problems below with backbone

1. backbone model is weak and error-prone in complex projects

when model is listened in many views like the accountModel, you can’t imagine what things will happen, it’s terrible.

2. missing data binding

this leads to every change should update view manual, it’s too disgusting.

3. hard to test

backbone’s event driven makes developer manipulate DOMs themselves,It’s hard to test.

4. code reuse

code can’t reuse effictively because of manipulating DOMs

Compared to react and the idea of flux, a new frontend technology stack comes. React is an awesome MVC View framework, and the flux programming idea is also greatly decoupling business which the above first problem can be solved.

Here is a redux and react demo - LianExchange

1. redux provide store with react

react router using history, the entry just bind redux store and react router, using history to enable router, using Provider to bind store with react

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Provider } from 'react-redux'
import { Router, Route } from 'react-router'
import { createHistory } from 'history'

import App from './containers/App'
import Buy from './containers/Buy'

import configure from './store'

const store = configure()
const history = createHistory()
syncReduxAndRouter(history, store)

//if you just want route with hash, just
//<Router location="hash"></Router>

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
      </Route>
      <Route path="/buy" component={Buy}>
      </Route>
    </Router>
  </Provider>,
  document.getElementById('root')
)

We can see how to generate redux store below and bind with react.

2. redux apply http request middleware and create store

Redux createStore can only handle synchronize action like the todo app, we using redux middleware to handle async action like ajax or some what.

1
2
3
4
5
6
7
8
9
10
11
12
13
  import { createStore, compose, applyMiddleware, combineReducers } from 'redux'
  import rootReducer from '../reducers'
  import apiMiddleware from '../middlewares/apiMiddleware'
  const create = window.devToolsExtension
    ? window.devToolsExtension()(createStore)
    : createStore

  const finalCreateStore = compose(
    applyMiddleware(apiMiddleware)
  )(create)

  // const store = create(rootReducer, initialState)
  const store = finalCreateStore(rootReducer)

here we create redux store, and bind to react, then how can we bind the redux state and action to react, look below, using redux connect and bindActionCreators

3. bind redux state and actions with react this.props

container/Buy/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as Coins from '../../actions/coins'
import style from './style.css'
import Header from '../../components/Header'
import Footer from '../../components/Footer'

class Buy extends Component {
  render() {
    const { actions, children, storage } = this.props

    return (
      <div>
        <Header />
        <Footer />
      </div>
    )
  }
}

function mapStateToProps(state) {
  return {
    storage: state.coins
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Coins, dispatch)
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Buy)

we can bind redux state the react components or just html5 pages- mapStateToProps, developers can dispatch actions or reading data from this.props, and the parent props can pass sub data using this.props

4. simple reducers describe state change process

reducers/account.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import ActionTypes from '../constants/ActionTypes'

const initState = {
  accountReady: false,
  accountError: false,
  account: {}
};

export default function account(state = initState, action) {
  switch (action.type) {
    case ActionTypes.ACCOUNT_LOAD: {
      return {
        ...state,
        accountReady: false
      }
    }
    case ActionTypes.ACCOUNT_LOAD_SUCCESS: {
      return {
        ...state,
        account: action.data,
        accountReady: true,
        accountError: false
      }
    }
    case ActionTypes.ACCOUNT_LOAD_ERROR: {
      return {
        ...state,
        account: action.data,
        accountReady: true,
        accountError: true
      }
    }
    default: {
      return state;
    }
  }
}

5. sync actions and async actions

actions/account.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import ActionTypes from '../constants/ActionTypes'
import {requestBase, baseAPI} from '../app/configs'

export function loadAccount() {
  return {
    types: [
      ActionTypes.ACCOUNT_LOAD,
      ActionTypes.ACCOUNT_LOAD_SUCCESS,
      ActionTypes.ACCOUNT_LOAD_ERROR
    ],
    requestSettings: {
      method: 'GET',
      url: requestBase.lianCoinUrl + baseAPI.me
    },
    requestParams: {Includes:['Accounts','Profile','BankCards']}
  }
}

Reducers only describe the data transfer process which how state A transfers to state B, and then the react components re-renders the view. Sync actions just like so, then reducers can change the state immediately

1
2
3
4
5
export function filterAccount() {
  return {
    type: ActionTypes.ACCOUNT_FILTER
  }
}

async actions should supply request url, method, params and so on

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function loadAccount() {
  return {
    types: [
      ActionTypes.ACCOUNT_LOAD,
      ActionTypes.ACCOUNT_LOAD_SUCCESS,
      ActionTypes.ACCOUNT_LOAD_ERROR
    ],
    requestSettings: {
      method: 'GET',
      url: requestBase.lianCoinUrl + baseAPI.me
    },
    requestParams: {Includes:['Accounts','Profile','BankCards']}
  }
}

6. redux http middleware

async actions generally has there types [load, success, error] correspond to the actions in middlewares/apiMiddleware.js const [PENDING, FULFILLED, REJECTED] = action.types, the the middleware do request using superagent or some other http request library you like, the callback can dispatch actions afterwards, the next is just redux dispatch, above in the containers/Buy/index.js we bind dispatch to the async action creator bindActionCreators(Coins, dispatch)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   case 'GET': {
      return superagent.get(url + '&' + querystring.stringify(params))
        .end((err, res) => {
          if(err || !res.body) {
            next({
              type: REJECTED,
              params
            })
          } else {
            next({
              type: FULFILLED,
              params,
              data: res.body.data
            })
          }
        })
    }

then reducers still accept actions and change the state, and afterwards react re-render views.