Skip to content

Instantly share code, notes, and snippets.

@keshavkaul
Last active August 4, 2018 09:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save keshavkaul/9bb67499029d63bc6745c8c23aee86f2 to your computer and use it in GitHub Desktop.
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.
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 };
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;
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