react-redux-with-path
Version:
A version of react-redux designed for cases that want to access sub-tree of the state. and take it as state in mapStateToProps and mapDispatchToProps
431 lines (359 loc) • 16.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
exports.default = connect;
var _react = require('react');
var _isPlainObject = require('lodash/isPlainObject');
var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
var _get = require('lodash/get');
var _get2 = _interopRequireDefault(_get);
var _hoistNonReactStatics = require('hoist-non-react-statics');
var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);
var _invariant = require('invariant');
var _invariant2 = _interopRequireDefault(_invariant);
var _utils = require('../utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var defaultMapStateToProps = function defaultMapStateToProps(state) {
return {};
}; // eslint-disable-line no-unused-vars
var defaultMapDispatchToProps = function defaultMapDispatchToProps(dispatch) {
return { dispatch: dispatch };
};
var defaultMergeProps = function defaultMergeProps(stateProps, dispatchProps, parentProps) {
return _extends({}, parentProps, stateProps, dispatchProps);
};
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
var errorObject = { value: null };
function tryCatch(fn, ctx) {
try {
return fn.apply(ctx);
} catch (e) {
errorObject.value = e;
return errorObject;
}
}
// Helps track hot reloading.
var nextVersion = 0;
function connect(mapStateToProps, mapDispatchToProps, mergeProps) {
var options = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
var shouldSubscribe = Boolean(mapStateToProps);
var mapState = mapStateToProps || defaultMapStateToProps;
var mapDispatch = void 0;
// if (typeof mapDispatchToProps === 'function') {
// mapDispatch = mapDispatchToProps
// } else if (!mapDispatchToProps) {
// mapDispatch = defaultMapDispatchToProps
// } else {
// mapDispatch = wrapActionCreators(mapDispatchToProps)
// }
var finalMergeProps = mergeProps || defaultMergeProps;
var _options$pure = options.pure;
var pure = _options$pure === undefined ? true : _options$pure;
var _options$withRef = options.withRef;
var withRef = _options$withRef === undefined ? false : _options$withRef;
var checkMergedEquals = pure && finalMergeProps !== defaultMergeProps;
// Helps track hot reloading.
var version = nextVersion++;
return function wrapWithConnect(WrappedComponent) {
var connectDisplayName = 'Connect(' + getDisplayName(WrappedComponent) + ')';
function checkStateShape(props, methodName) {
if (!(0, _isPlainObject2.default)(props)) {
(0, _utils.warning)(methodName + '() in ' + connectDisplayName + ' must return a plain object. ' + ('Instead received ' + props + '.'));
}
}
function computeMergedProps(stateProps, dispatchProps, parentProps) {
var mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps);
if (process.env.NODE_ENV !== 'production') {
checkStateShape(mergedProps, 'mergeProps');
}
return mergedProps;
}
var Connect = function (_Component) {
_inherits(Connect, _Component);
_createClass(Connect, [{
key: 'shouldComponentUpdate',
value: function shouldComponentUpdate() {
return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged;
}
}]);
function Connect(props, context) {
_classCallCheck(this, Connect);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Connect).call(this, props, context));
_this.version = version;
_this.store = props.store || context.store;
_this.path = props.path || context.path;
(0, _invariant2.default)(_this.store, 'Could not find "store" in either the context or ' + ('props of "' + connectDisplayName + '". ') + 'Either wrap the root component in a <Provider>, ' + ('or explicitly pass "store" as a prop to "' + connectDisplayName + '".'));
var storeState = _this.getSubstate();
_this.state = { storeState: storeState };
_this.clearCache();
return _this;
}
_createClass(Connect, [{
key: 'computeStateProps',
value: function computeStateProps(store, props) {
if (!this.finalMapStateToProps) {
return this.configureFinalMapState(store, props);
}
var state = this.getSubstate();
var stateProps = this.doStatePropsDependOnOwnProps ? this.finalMapStateToProps(state, props) : this.finalMapStateToProps(state);
if (process.env.NODE_ENV !== 'production') {
checkStateShape(stateProps, 'mapStateToProps');
}
return stateProps;
}
}, {
key: 'configureFinalMapState',
value: function configureFinalMapState(store, props) {
var mappedState = mapState(this.getSubstate(), props);
var isFactory = typeof mappedState === 'function';
this.finalMapStateToProps = isFactory ? mappedState : mapState;
this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1;
if (isFactory) {
return this.computeStateProps(store, props);
}
if (process.env.NODE_ENV !== 'production') {
checkStateShape(mappedState, 'mapStateToProps');
}
return mappedState;
}
}, {
key: 'computeDispatchProps',
value: function computeDispatchProps(store, props) {
if (!this.finalMapDispatchToProps) {
return this.configureFinalMapDispatch(store, props);
}
var dispatch = store.dispatch;
var dispatchProps = this.doDispatchPropsDependOnOwnProps ? this.finalMapDispatchToProps(dispatch, props) : this.finalMapDispatchToProps(dispatch);
if (process.env.NODE_ENV !== 'production') {
checkStateShape(dispatchProps, 'mapDispatchToProps');
}
return dispatchProps;
}
}, {
key: 'configureFinalMapDispatch',
value: function configureFinalMapDispatch(store, props) {
// move from line 42 to here because we need the path now: start
if (typeof mapDispatchToProps === 'function') {
mapDispatch = mapDispatchToProps;
} else if (!mapDispatchToProps) {
mapDispatch = defaultMapDispatchToProps;
} else {
mapDispatch = (0, _utils.wrapActionCreators)(mapDispatchToProps, this.path);
}
var mappedDispatch = mapDispatch(store.dispatch, props);
// end
var isFactory = typeof mappedDispatch === 'function';
this.finalMapDispatchToProps = isFactory ? mappedDispatch : mapDispatch;
this.doDispatchPropsDependOnOwnProps = this.finalMapDispatchToProps.length !== 1;
if (isFactory) {
return this.computeDispatchProps(store, props);
}
if (process.env.NODE_ENV !== 'production') {
checkStateShape(mappedDispatch, 'mapDispatchToProps');
}
return mappedDispatch;
}
}, {
key: 'updateStatePropsIfNeeded',
value: function updateStatePropsIfNeeded() {
var nextStateProps = this.computeStateProps(this.store, this.props);
if (this.stateProps && (0, _utils.shallowEqual)(nextStateProps, this.stateProps)) {
return false;
}
this.stateProps = nextStateProps;
return true;
}
}, {
key: 'updateDispatchPropsIfNeeded',
value: function updateDispatchPropsIfNeeded() {
var nextDispatchProps = this.computeDispatchProps(this.store, this.props);
if (this.dispatchProps && (0, _utils.shallowEqual)(nextDispatchProps, this.dispatchProps)) {
return false;
}
this.dispatchProps = nextDispatchProps;
return true;
}
}, {
key: 'updateMergedPropsIfNeeded',
value: function updateMergedPropsIfNeeded() {
var nextMergedProps = computeMergedProps(this.stateProps, this.dispatchProps, this.props);
if (this.mergedProps && checkMergedEquals && (0, _utils.shallowEqual)(nextMergedProps, this.mergedProps)) {
return false;
}
this.mergedProps = nextMergedProps;
return true;
}
}, {
key: 'isSubscribed',
value: function isSubscribed() {
return typeof this.unsubscribe === 'function';
}
}, {
key: 'trySubscribe',
value: function trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this));
this.handleChange();
}
}
}, {
key: 'tryUnsubscribe',
value: function tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe();
this.unsubscribe = null;
}
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
this.trySubscribe();
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
if (!pure || !(0, _utils.shallowEqual)(nextProps, this.props)) {
this.haveOwnPropsChanged = true;
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.tryUnsubscribe();
this.clearCache();
}
}, {
key: 'clearCache',
value: function clearCache() {
this.dispatchProps = null;
this.stateProps = null;
this.mergedProps = null;
this.haveOwnPropsChanged = true;
this.hasStoreStateChanged = true;
this.haveStatePropsBeenPrecalculated = false;
this.statePropsPrecalculationError = null;
this.renderedElement = null;
this.finalMapDispatchToProps = null;
this.finalMapStateToProps = null;
}
}, {
key: 'handleChange',
value: function handleChange() {
if (!this.unsubscribe) {
return;
}
var storeState = this.getSubstate();
var prevStoreState = this.state.storeState;
if (pure && prevStoreState === storeState) {
return;
}
if (pure && !this.doStatePropsDependOnOwnProps) {
var haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this);
if (!haveStatePropsChanged) {
return;
}
if (haveStatePropsChanged === errorObject) {
this.statePropsPrecalculationError = errorObject.value;
}
this.haveStatePropsBeenPrecalculated = true;
}
this.hasStoreStateChanged = true;
this.setState({ storeState: storeState });
}
}, {
key: 'getWrappedInstance',
value: function getWrappedInstance() {
(0, _invariant2.default)(withRef, 'To access the wrapped instance, you need to specify ' + '{ withRef: true } as the fourth argument of the connect() call.');
return this.refs.wrappedInstance;
}
}, {
key: 'getSubstate',
value: function getSubstate() {
if (!this.path) {
return this.store.getState();
}
return (0, _get2.default)(this.store.getState(), this.path);
}
}, {
key: 'render',
value: function render() {
var haveOwnPropsChanged = this.haveOwnPropsChanged;
var hasStoreStateChanged = this.hasStoreStateChanged;
var haveStatePropsBeenPrecalculated = this.haveStatePropsBeenPrecalculated;
var statePropsPrecalculationError = this.statePropsPrecalculationError;
var renderedElement = this.renderedElement;
this.haveOwnPropsChanged = false;
this.hasStoreStateChanged = false;
this.haveStatePropsBeenPrecalculated = false;
this.statePropsPrecalculationError = null;
if (statePropsPrecalculationError) {
throw statePropsPrecalculationError;
}
var shouldUpdateStateProps = true;
var shouldUpdateDispatchProps = true;
if (pure && renderedElement) {
shouldUpdateStateProps = hasStoreStateChanged || haveOwnPropsChanged && this.doStatePropsDependOnOwnProps;
shouldUpdateDispatchProps = haveOwnPropsChanged && this.doDispatchPropsDependOnOwnProps;
}
var haveStatePropsChanged = false;
var haveDispatchPropsChanged = false;
if (haveStatePropsBeenPrecalculated) {
haveStatePropsChanged = true;
} else if (shouldUpdateStateProps) {
haveStatePropsChanged = this.updateStatePropsIfNeeded();
}
if (shouldUpdateDispatchProps) {
haveDispatchPropsChanged = this.updateDispatchPropsIfNeeded();
}
var haveMergedPropsChanged = true;
if (haveStatePropsChanged || haveDispatchPropsChanged || haveOwnPropsChanged) {
haveMergedPropsChanged = this.updateMergedPropsIfNeeded();
} else {
haveMergedPropsChanged = false;
}
if (!haveMergedPropsChanged && renderedElement) {
return renderedElement;
}
if (withRef) {
this.renderedElement = (0, _react.createElement)(WrappedComponent, _extends({}, this.mergedProps, {
ref: 'wrappedInstance'
}));
} else {
this.renderedElement = (0, _react.createElement)(WrappedComponent, this.mergedProps);
}
return this.renderedElement;
}
}]);
return Connect;
}(_react.Component);
Connect.displayName = connectDisplayName;
Connect.WrappedComponent = WrappedComponent;
Connect.contextTypes = {
path: _utils.pathType,
store: _utils.storeShape
};
Connect.propTypes = {
store: _utils.storeShape
};
if (process.env.NODE_ENV !== 'production') {
Connect.prototype.componentWillUpdate = function componentWillUpdate() {
if (this.version === version) {
return;
}
// We are hot reloading!
this.version = version;
this.trySubscribe();
this.clearCache();
};
}
return (0, _hoistNonReactStatics2.default)(Connect, WrappedComponent);
};
}