Last active
October 23, 2016 09:34
-
-
Save gevgeny/d43766dbe585920b473e3bd2a95f6cdc to your computer and use it in GitHub Desktop.
One more try to implement react-redux app with fractal architecture to localize ui specific state
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { reducer as ui } from './ui' | |
const rootReducer = combineReducers({ | |
ui | |
// other reducers | |
}) | |
export default rootReducer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { connectUi } from './ui' | |
import reducer, { increment } from './reducer' | |
export default const compose( | |
connectUi( | |
{ reducer, initialState: { value: 0 } }, | |
({ value }) => ({ value }), | |
(dispatch, { uiKey }) => ({ | |
onIncrement: item => dispatch(increment(uiKey)) | |
}) | |
) | |
)(({ name, value, onIncrement }) => ( | |
<div> | |
<span>{value}</span> | |
<button onClick={onIncrement}></button> | |
</div> | |
)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import IncrementBox from './IncrementBox.jsx' | |
render( | |
<div> | |
<IncrementBox uiKey="foo"/> | |
<IncrementBox uiKey="bar"/> | |
<IncrementBox uiKey="baz"/> | |
</div> | |
, document.getElementById('root') | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default (state = {}, action) => { | |
switch (action.type) { | |
case 'ui/INCREMENT': | |
return { value: state.value + 1 } | |
default: | |
return state; | |
} | |
} | |
export const increment = (uiKey) => ({ | |
type: 'ui/INCREMENT', | |
meta: { uiKey } | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, PropTypes } from 'react' | |
import { connect } from 'react-redux' | |
import toClass from 'recompose/toClass' | |
import compose from 'recompose/compose' | |
import reduce from 'lodash/reduce' | |
import uniqueId from 'lodash/uniqueId' | |
import omit from 'lodash/omit' | |
const MOUNT = 'ui/MOUN' | |
const UNMOUNT = 'ui/UNMOUNT' | |
const dynamicReducers = {} | |
// Static reducer which should be mounted to the store | |
// But composes dynamic list of reducers | |
// which are being added and removed during components lifecycle | |
export const reducer = (state = {}, action) => { | |
if (!action.type.startsWith('ui/')) { | |
return state | |
} | |
if (action.type === MOUNT) { | |
return { | |
...state, | |
[action.payload.key]: action.payload.initialState | |
} | |
} else if (action.type === UNMOUNT) { | |
return omit(state, action.payload) | |
} else if (action.meta && action.meta.uiKey) { | |
// Run specific reducer by provided key | |
const key = action.meta.uiKey | |
return { | |
...state, | |
[key]: dynamicReducers[key](state[key], action) | |
} | |
} else { | |
// Run all the UI reducers if the key is not provided | |
return reduce(dynamicReducers, (newState, reducer, key) => ({ | |
...newState, | |
[key]: dynamicReducers[key](newState[key], action) | |
}), state) | |
} | |
} | |
export const connectUi = ( | |
{ reducer, initialState }, | |
mapStateToProps, | |
mapDispatchToProps | |
) => compose( | |
Target => class extends Component { | |
static contextTypes = { | |
store: PropTypes.shape({ | |
dispatch: PropTypes.func.isRequired | |
}) | |
} | |
static displayName = `ConnectUi(${Target.displayName || Target.name})` | |
componentWillMount() { | |
this.uiKey = this.props.uiKey || uniqueId() | |
if (dynamicReducers[this.uiKey]) { | |
throw new Error(`Component with key ${this.uiKey} already exists`) | |
} | |
dynamicReducers[this.uiKey] = reducer | |
this.context.store.dispatch({ | |
type: MOUNT, | |
payload: { initialState, key: this.uiKey} | |
}) | |
} | |
componentWillUnmount() { | |
delete dynamicReducers[this.uiKey] | |
this.context.store.dispatch({ type: UNMOUNT, payload : this.uiKey }) | |
} | |
render() { | |
return <Target uiKey = {this.uiKey} {...this.props}/> | |
} | |
}, | |
connect( | |
(state, { uiKey }) => state.ui[uiKey] || { }, | |
mapDispatchToProps | |
) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment