Subscribe or follow on X for updates when new posts go live.
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