react-refetch
Version:
A simple, declarative, and composable way to fetch data for React components.
301 lines (225 loc) • 11.5 kB
JavaScript
;
exports.__esModule = true;
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;
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 _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; }
require('whatwg-fetch');
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _utilsIsPlainObject = require('../utils/isPlainObject');
var _utilsIsPlainObject2 = _interopRequireDefault(_utilsIsPlainObject);
var _utilsDeepValue = require('../utils/deepValue');
var _utilsDeepValue2 = _interopRequireDefault(_utilsDeepValue);
var _utilsShallowEqual = require('../utils/shallowEqual');
var _utilsShallowEqual2 = _interopRequireDefault(_utilsShallowEqual);
var _PromiseState = require('../PromiseState');
var _PromiseState2 = _interopRequireDefault(_PromiseState);
var _hoistNonReactStatics = require('hoist-non-react-statics');
var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);
var _invariant = require('invariant');
var _invariant2 = _interopRequireDefault(_invariant);
var defaultMapPropsToRequestsToProps = function defaultMapPropsToRequestsToProps() {
return {};
};
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
// Helps track hot reloading.
var nextVersion = 0;
function connect(mapPropsToRequestsToProps) {
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var finalMapPropsToRequestsToProps = mapPropsToRequestsToProps || defaultMapPropsToRequestsToProps;
var _options$withRef = options.withRef;
var withRef = _options$withRef === undefined ? false : _options$withRef;
// Helps track hot reloading.
var version = nextVersion++;
function coerceMappings(rawMappings) {
_invariant2['default'](_utilsIsPlainObject2['default'](rawMappings), '`mapPropsToRequestsToProps` must return an object. Instead received %s.', rawMappings);
var mappings = {};
Object.keys(rawMappings).forEach(function (prop) {
mappings[prop] = coerceMapping(prop, rawMappings[prop]);
});
return mappings;
}
function coerceMapping(prop, mapping) {
if (Function.prototype.isPrototypeOf(mapping)) {
return mapping;
}
if (typeof mapping === 'string') {
mapping = { url: mapping };
}
_invariant2['default'](_utilsIsPlainObject2['default'](mapping), 'Request for `%s` must be either a string or a plain object. Instead received %s', prop, mapping);
_invariant2['default'](mapping.url, 'Request object for `%s` must have `url` attribute.', prop);
mapping.equals = (function (that) {
var _this = this;
if (this.comparison !== undefined) {
return this.comparison === that.comparison;
}
return ['url', 'method', 'headers', 'body'].every(function (c) {
return _utilsShallowEqual2['default'](_utilsDeepValue2['default'](_this, c), _utilsDeepValue2['default'](that, c));
});
}).bind(mapping);
return mapping;
}
function buildRequest(mapping) {
return new window.Request(mapping.url, {
method: mapping.method || 'GET',
headers: Object.assign({
'Accept': 'application/json',
'Content-Type': 'application/json'
}, mapping.headers),
credentials: mapping.credentials || 'same-origin',
body: mapping.body
});
}
function handleResponse(response) {
var json = response.json(); // TODO: support other response types
if (response.status >= 200 && response.status < 300) {
// TODO: support custom acceptable statuses
return json;
} else {
return json.then(function (errorJson) {
var id = errorJson.id;
var error = errorJson.error;
var message = errorJson.message;
if (error) {
throw new Error(error, id);
} else if (message) {
throw new Error(message, id);
} else {
throw new Error(errorJson);
}
});
}
}
return function wrapWithConnect(WrappedComponent) {
var RefetchConnect = (function (_Component) {
_inherits(RefetchConnect, _Component);
function RefetchConnect(props, context) {
_classCallCheck(this, RefetchConnect);
_Component.call(this, props, context);
this.version = version;
this.state = { mappings: {}, startedAts: {}, data: {}, refreshTimeouts: {} };
}
RefetchConnect.prototype.componentWillMount = function componentWillMount() {
this.refetchDataFromProps();
};
RefetchConnect.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
this.refetchDataFromProps(nextProps);
};
RefetchConnect.prototype.componentWillUnmount = function componentWillUnmount() {
this.clearAllRefreshTimeouts();
};
RefetchConnect.prototype.render = function render() {
var ref = withRef ? 'wrappedInstance' : null;
return _react2['default'].createElement(WrappedComponent, _extends({}, this.state.data, this.props, { ref: ref }));
};
RefetchConnect.prototype.getWrappedInstance = function getWrappedInstance() {
_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;
};
RefetchConnect.prototype.refetchDataFromProps = function refetchDataFromProps() {
var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
this.refetchDataFromMappings(finalMapPropsToRequestsToProps(props) || {});
};
RefetchConnect.prototype.refetchDataFromMappings = function refetchDataFromMappings(mappings) {
var _this2 = this;
mappings = coerceMappings(mappings);
Object.keys(mappings).forEach(function (prop) {
var mapping = mappings[prop];
if (Function.prototype.isPrototypeOf(mapping)) {
_this2.setAtomicState(prop, new Date(), mapping, function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this2.refetchDataFromMappings(mapping.apply(undefined, args || {}));
});
return;
}
if (mapping.force || !mapping.equals(_this2.state.mappings[prop] || {})) {
_this2.refetchDatum(prop, mapping);
}
});
};
RefetchConnect.prototype.refetchDatum = function refetchDatum(prop, mapping) {
var _this3 = this;
var startedAt = new Date();
if (this.state.refreshTimeouts[prop]) {
window.clearTimeout(this.state.refreshTimeouts[prop]);
}
var request = buildRequest(mapping);
var meta = { request: request };
var initPS = mapping.refreshing ? _PromiseState2['default'].refresh(this.state.data[prop], meta) : _PromiseState2['default'].create(meta);
this.setAtomicState(prop, startedAt, mapping, initPS, null);
window.fetch(request).then(function (response) {
meta.response = response;
return Promise.resolve(response).then(handleResponse).then(function (value) {
var refreshTimeout = null;
if (mapping.refreshInterval > 0) {
refreshTimeout = window.setTimeout(function () {
_this3.refetchDatum(prop, Object.assign({}, mapping, { refreshing: true, force: true }));
}, mapping.refreshInterval);
}
if (Function.prototype.isPrototypeOf(mapping.then)) {
_this3.refetchDatum(prop, coerceMapping(null, mapping.then(value, meta)));
return;
}
_this3.setAtomicState(prop, startedAt, mapping, _PromiseState2['default'].resolve(value, meta), refreshTimeout, function () {
if (Function.prototype.isPrototypeOf(mapping.andThen)) {
_this3.refetchDataFromMappings(mapping.andThen(value, meta));
}
});
})['catch'](function (reason) {
if (Function.prototype.isPrototypeOf(mapping['catch'])) {
_this3.refetchDatum(coerceMapping(null, mapping['catch'](reason, meta)));
return;
}
_this3.setAtomicState(prop, startedAt, mapping, _PromiseState2['default'].reject(reason, meta), null, function () {
if (Function.prototype.isPrototypeOf(mapping.andCatch)) {
_this3.refetchDataFromMappings(mapping.andCatch(reason, meta));
}
});
});
});
};
RefetchConnect.prototype.setAtomicState = function setAtomicState(prop, startedAt, mapping, datum, refreshTimeout, callback) {
this.setState(function (prevState) {
var _Object$assign, _Object$assign2, _Object$assign3, _Object$assign4;
if (startedAt < prevState.startedAts[prop]) {
return {};
}
return {
startedAts: Object.assign(prevState.startedAts, (_Object$assign = {}, _Object$assign[prop] = startedAt, _Object$assign)),
mappings: Object.assign(prevState.mappings, (_Object$assign2 = {}, _Object$assign2[prop] = mapping, _Object$assign2)),
data: Object.assign(prevState.data, (_Object$assign3 = {}, _Object$assign3[prop] = datum, _Object$assign3)),
refreshTimeouts: Object.assign(prevState.refreshTimeouts, (_Object$assign4 = {}, _Object$assign4[prop] = refreshTimeout, _Object$assign4))
};
}, callback);
};
RefetchConnect.prototype.clearAllRefreshTimeouts = function clearAllRefreshTimeouts() {
var _this4 = this;
Object.keys(this.state.refreshTimeouts).forEach(function (prop) {
clearTimeout(_this4.state.refreshTimeouts[prop]);
});
};
return RefetchConnect;
})(_react.Component);
RefetchConnect.displayName = 'Refetch.connect(' + getDisplayName(WrappedComponent) + ')';
RefetchConnect.WrappedComponent = WrappedComponent;
if (process.env.NODE_ENV !== 'production') {
RefetchConnect.prototype.componentWillUpdate = function componentWillUpdate() {
if (this.version === version) {
return;
}
// We are hot reloading!
this.version = version;
this.clearAllRefreshTimeouts();
this.refetchDataFromProps();
};
}
return _hoistNonReactStatics2['default'](RefetchConnect, WrappedComponent);
};
}
module.exports = exports['default'];