Since I talk about a good amount of React / Redux work on here, I thought it'd be appropriate to share the patterns I like to use when I build React / Redux applications.
I'll break down my patterns into the various concepts React and Redux provide.
I'm sure I can probably go more into detail about the intracacies of each, or how they differ from what Redux recommends whatever. I really like what React and Redux are doing so I'm not writing any specific implementation because I disagree with Redux - I do it because it works for me and it creates less work.
Maybe I'll revise this post at some point.
import Container from './containers/AppContainer' import configureStore from './store/configureStore' import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' const store = configureStore() render( <Provider store={store}> <Container /> </Provider>, document.getElementById('app') )
import actionListener from './middleware/listeners/Action' import pageMiddleware from './middleware/Page' import listenerMiddleware from './middleware/Listener' import rootReducer from './reducers' import { applyMiddleware, createStore } from 'redux' const configureStore = (initialState) => { const middleware = [ pageMiddleware, listenerMiddleware( actionListener, ) ] const store = createStore(rootReducer, initialState, applyMiddleware(...middleware)) return store } export default configureStore
import App from './App' import { combineReducers } from 'redux' const rootReducer = combineReducers({ app: App, }) export default rootReducer
import * as types from './constants/ActionTypes' export const createReducer = (initialState, handlers) => { return (state = initialState, action) => { if (typeof handlers[action.type] !== 'undefined') { return handlers[action.type](state, action) } else { return state } } } //... export const reduxActionTypeReducer = (state, action) => { return { ...state, errors: {}, } } //... const reducers = {} reducers[types.REDUX_ACTION_TYPE] = reduxActionTypeReducer const appReducer = createReducer(getInitialState(), reducers) export default appReducer
import * as Actions from './actions' import * as types from './constants/ActionTypes' export default { //... [types.REDUX_ACTION_TYPE]: (action, dispatch, state) => { dispatch(Actions.reduxActionTypeAction(action)) dispatch(Actions.reduxAjaxActionTypeAction({ next: () => { console.log('Hello World') } })) }, //... }
import * as Actions from '../../actions' import * as types from '../../constants/ActionTypes' const ajaxAsync = (url, method = 'GET') => { return async (action, dispatch, state) => { const { fail = Actions.setLoadingError, next, payload } = action const xhrApiMap = { DELETE: xhrDelete, GET: xhrGet, POST: xhrPost, PUT: xhrPut, } const xhrApiFunc = xhrApiMap[method] if (typeof xhrApiFunc === 'undefined') { throw new Error('Unsupported HTTP Method') } let query let body if (typeof payload !== 'undefined' && (method === 'PUT' || method === 'POST')) { body = payload } if (typeof payload !== 'undefined' && (method === 'DELETE' || method === 'GET')) { query = payload } const [error, response] = await xhrApiFunc({ body, query, url, }) if (typeof error !== 'undefined') { dispatch( fail({ config: { method, payload, }, error, url, }) ) return } // Received a signal to refresh the domain if (typeof response !== 'undefined' && response.refreshDomain === true) { dispatch(Actions.getShoppingCart()) return } if (typeof next === 'function') { dispatch(next(response)) } } } export default { [types.AJAX_REDUX_ACTION_TYPE]: ajaxAsync('/api'), // ... }
import * as types from './constants/ActionTypes' //... export const reduxActionTypeAction = ({ fail, next, payload }) => ({ fail, next, payload, type: types.REDUX_ACTION_TYPE, }) export const reduxAjaxActionTypeAction = ({ fail, next, payload }) => ({ fail, next, payload, type: types.AJAX_REDUX_ACTION_TYPE, }) //...
export const REDUX_ACTION_TYPE = 'REDUX_ACTION_TYPE' export const REDUX_ACTION_TYPE_2 = 'REDUX_ACTION_TYPE_2' //...
export default (...listeners) => (store) => (next) => (action) => { // listeners are provided with a picture // of the world before the action is applied const preActionState = store.getState() // release the action to reducers before // firing additional actions next(action) // always async setTimeout(() => { listeners.forEach((listener) => { if (listener[action.type]) { listener[action.type](action, store.dispatch, preActionState) } }) }) }
import React from 'react' import * as Actions from './actions' import ReactComponent from './components/ReactComponent' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' class Container extends React.Component { render() { const boundActions = bindActionCreators(Actions, this.props.dispatch) return <ReactComponent {...this.props} {...boundActions} /> } } const mapStateToProps = (state) => { const { app } = state return { app } } export default connect(mapStateToProps)(Container)
import React from 'react' const ReactComponent = (p) => ( <div className="some-className"> <button onClick={p.reduxActionTypeAction}> Click Me </button> </div> } export default ReactComponent