Last active
August 4, 2018 09:13
-
-
Save keshavkaul/9bb67499029d63bc6745c8c23aee86f2 to your computer and use it in GitHub Desktop.
A react redux connect API inspired by `react-redux` library using latest React features.
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 * as React from 'react'; | |
const StoreContext = React.createContext(); | |
const StoreProvider = StoreContext.Provider; | |
const StoreConsumer = StoreContext.Consumer; | |
const isShallowEqual = (objA, objB) => { | |
const hasOwn = Object.prototype.hasOwnProperty | |
function is(x, y) { | |
if (x === y) { | |
return x !== 0 || y !== 0 || 1 / x === 1 / y | |
} else { | |
return x !== x && y !== y | |
} | |
} | |
if (is(objA, objB)) return true | |
if (typeof objA !== 'object' || objA === null || | |
typeof objB !== 'object' || objB === null) { | |
return false | |
} | |
const keysA = Object.keys(objA) | |
const keysB = Object.keys(objB) | |
if (keysA.length !== keysB.length) return false | |
for (let i = 0; i < keysA.length; i++) { | |
if (!hasOwn.call(objB, keysA[i]) || | |
!is(objA[keysA[i]], objB[keysA[i]])) { | |
return false | |
} | |
} | |
return true | |
} | |
const initMapDispatchToProps = (mapDispatchToProps) => { | |
if(!mapDispatchToProps) return undefined; | |
switch(typeof(mapDispatchToProps)) { | |
case 'function': | |
return mapDispatchToProps; | |
case 'object': | |
return Object.keys(mapDispatchToProps) | |
.reduce((last, key) => { | |
last[key] = (...args) => { | |
last[key].dispatch(mapDispatchToProps[key](...args, last[key].ownProps)) | |
}; | |
return last; | |
}, {}); | |
} | |
} | |
const connect = (mapStateToProps = null, mapDispatchToProps = null, mergeProps = null) => { | |
const _mergeProps = (stateProps, dispatchProps, ownProps) => { | |
if (mergeProps) { | |
return mergeProps(stateProps, dispatchProps, ownProps); | |
} | |
return { ...ownProps, ...stateProps, ...dispatchProps}; | |
} | |
const _mapStateToProps = (state, ownProps) => { | |
if (mapStateToProps) { | |
return mapStateToProps(state, ownProps); | |
} | |
return {}; | |
} | |
let actionCreatorMap = initMapDispatchToProps(mapDispatchToProps); | |
const _mapDispatchToProps = (dispatch, ownProps) => { | |
switch(typeof(actionCreatorMap)) { | |
case 'function': | |
return actionCreatorMap(dispatch, ownProps); | |
case 'object': | |
Object.keys(actionCreatorMap) | |
.forEach(key => { | |
actionCreatorMap[key].dispatch = dispatch; | |
actionCreatorMap[key].ownProps = ownProps; | |
}); | |
return actionCreatorMap; | |
default: | |
return { dispatch }; | |
} | |
} | |
const makeStateGetter = (getState, dispatch) => (ownProps) => { | |
return { | |
..._mergeProps( | |
_mapStateToProps(getState(), ownProps), | |
_mapDispatchToProps(dispatch, ownProps), | |
ownProps | |
) | |
} | |
} | |
return (ContainerComponent) => { | |
class StoreComponent extends React.PureComponent { | |
constructor(props) { | |
super(props); | |
const { store } = this.props; | |
this.getComponentState = makeStateGetter(store.getState, store.dispatch); | |
const initialState = this.getComponentState(this.getOwnProps(this.props)); | |
this.state = { | |
...initialState | |
}; | |
} | |
componentDidMount() { | |
const { store } = this.props; | |
this.unsubscribeFromStore = store.subscribe(() => { | |
this.setState((prevState, props) => { | |
const newState = this.getComponentState(this.getOwnProps(props)); | |
return isShallowEqual(prevState, newState) ? null : newState; | |
}); | |
}); | |
} | |
componentWillUnmount() { | |
this.unsubscribeFromStore && this.unsubscribeFromStore(); | |
actionCreatorMap = undefined; | |
} | |
getOwnProps(props) { | |
const { store, forwardedRef, ...otherProps } = props; | |
return otherProps; | |
} | |
render() { | |
const { forwardedRef } = this.props; | |
const newProps = { ...this.getOwnProps(this.props), ...this.state }; | |
return ( | |
<ContainerComponent ref={forwardedRef} {...newProps}/> | |
); | |
} | |
} | |
return React.forwardRef((props, ref) => { | |
return ( | |
<StoreConsumer> | |
{ | |
({ store }) => { | |
return ( | |
<StoreComponent forwardedRef={ref} {...props} store={store}/> | |
); | |
} | |
} | |
</StoreConsumer> | |
); | |
}); | |
} | |
} | |
export { connect, StoreProvider }; |
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, { PureComponent } from 'react'; | |
import { | |
View, | |
Text, | |
Button, | |
TextInput | |
} from 'react-native'; | |
import { connect } from './connect'; | |
class CounterView extends PureComponent { | |
render() { | |
const { count, step, increment, decrement, changeStep } = this.props; | |
console.log(this.props); | |
return ( | |
<View style={{flex: 1}}> | |
<View style={{height: 100}}/> | |
<View style={{flex: 2, alignItems: 'center', justifyContent: 'center'}}> | |
<Text>{`${count}`}</Text> | |
<TextInput value={`${step}`} onChangeText={(txt) => changeStep(Number.parseInt(txt))}/> | |
<Button title="Increment" onPress={() => increment(step)}/> | |
<Button title="Decrement" onPress={() => decrement(step)}/> | |
</View> | |
</View> | |
); | |
} | |
} | |
const changeStep = (step) => { | |
return { | |
type: 'CHANGE_STEP', | |
payload: step | |
} | |
} | |
const increment = (step) => { | |
return { | |
type: 'INC', | |
payload: step | |
} | |
} | |
const decrement = (step) => { | |
return { | |
type: 'DEC', | |
payload: step | |
}; | |
} | |
CounterView = connect( | |
(state, props) => { | |
return { | |
count: state.count, | |
step: state.step, | |
}; | |
}, | |
{ | |
changeStep, | |
increment, | |
decrement | |
} | |
)(CounterView); | |
export default CounterView; |
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 { StoreProvider } from './connect'; | |
import CounterView from './example-counter-view'; | |
// Normal redux store creation | |
const store = configureStore(); | |
export default class App extends Component { | |
render() { | |
return ( | |
<StoreProvider value={{store}}> | |
<CounterView/> | |
</StoreProvider> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment