react-url-query
Version:
A library for managing state through query parameters in the URL in React. Works well with or without Redux and React Router.
276 lines (203 loc) • 12 kB
JavaScript
;
exports.__esModule = 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; }; }();
exports.default = addUrlProps;
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _queryString = require('query-string');
var _urlQueryDecoder = require('../urlQueryDecoder');
var _urlQueryDecoder2 = _interopRequireDefault(_urlQueryDecoder);
var _urlQueryConfig = require('../urlQueryConfig');
var _urlQueryConfig2 = _interopRequireDefault(_urlQueryConfig);
var _updateUrlQuery = require('../updateUrlQuery');
var _serialize = require('../serialize');
var _UrlUpdateTypes = require('../UrlUpdateTypes');
var _UrlUpdateTypes2 = _interopRequireDefault(_UrlUpdateTypes);
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; }
/**
* Higher order component (HOC) that injects URL query parameters as props.
*
* @param {Function} mapUrlToProps `function(url, props) -> {Object}` returns props to inject
* @return {React.Component}
*/
function addUrlProps() {
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _options$mapUrlToProp = options.mapUrlToProps;
var mapUrlToProps = _options$mapUrlToProp === undefined ? function (d) {
return d;
} : _options$mapUrlToProp;
var mapUrlChangeHandlersToProps = options.mapUrlChangeHandlersToProps;
var urlPropsQueryConfig = options.urlPropsQueryConfig;
var addRouterParams = options.addRouterParams;
var addUrlChangeHandlers = options.addUrlChangeHandlers;
var changeHandlerName = options.changeHandlerName;
return function addPropsWrapper(WrappedComponent) {
// caching to prevent unnecessary generation of new onChange functions
var cachedHandlers = void 0;
var decodeQuery = void 0;
// initialize decode query (with cache) if a config is provided
if (urlPropsQueryConfig) {
decodeQuery = (0, _urlQueryDecoder2.default)(urlPropsQueryConfig);
}
/**
* Parse the URL query into an object. If a urlPropsQueryConfig is provided
* the values are decoded based on type.
*/
function getUrlObject(props) {
var location = void 0;
// check in history
if (_urlQueryConfig2.default.history.location) {
location = _urlQueryConfig2.default.history.location;
// react-router provides it as a prop
} else if (props.location && (props.location.query || props.location.search != null)) {
location = props.location;
// not found. just use location from window
} else {
location = window.location;
}
var currentQuery = location.query || (0, _queryString.parse)(location.search) || {};
var result = void 0;
// if a url query decoder is provided, decode the query then return that.
if (decodeQuery) {
result = decodeQuery(currentQuery);
} else {
result = currentQuery;
}
// add in react-router params if requested
if (addRouterParams || addRouterParams !== false && _urlQueryConfig2.default.addRouterParams) {
Object.assign(result, props.params, props.match && props.match.params);
}
return result;
}
var displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
var AddUrlProps = function (_Component) {
_inherits(AddUrlProps, _Component);
function AddUrlProps() {
_classCallCheck(this, AddUrlProps);
return _possibleConstructorReturn(this, (AddUrlProps.__proto__ || Object.getPrototypeOf(AddUrlProps)).apply(this, arguments));
}
_createClass(AddUrlProps, [{
key: 'getUrlChangeHandlerProps',
/**
* Create URL change handlers based on props, the urlPropsQueryConfig (if provided),
* and mapUrlChangeHandlersToProps (if provided).
* As a member function so we can read `this.props` in generated handlers dynamically.
*/
value: function getUrlChangeHandlerProps(propsWithUrl) {
var _this2 = this;
var handlers = void 0;
if (urlPropsQueryConfig) {
// if we have a props->query config, generate the change handler props unless
// addUrlChangeHandlers is false
if (addUrlChangeHandlers || addUrlChangeHandlers == null && _urlQueryConfig2.default.addUrlChangeHandlers) {
// use cache if available. Have to do this since urlQueryConfig can change between
// renders (although that is unusual).
if (cachedHandlers) {
handlers = cachedHandlers;
} else {
// read in function from options for how to generate a name from a prop
if (!changeHandlerName) {
changeHandlerName = _urlQueryConfig2.default.changeHandlerName;
}
// for each URL config prop, create a handler
handlers = Object.keys(urlPropsQueryConfig).reduce(function (handlersAccum, propName) {
var _urlPropsQueryConfig$ = urlPropsQueryConfig[propName];
var updateType = _urlPropsQueryConfig$.updateType;
var _urlPropsQueryConfig$2 = _urlPropsQueryConfig$.queryParam;
var queryParam = _urlPropsQueryConfig$2 === undefined ? propName : _urlPropsQueryConfig$2;
var type = _urlPropsQueryConfig$.type;
// name handler for `foo` => `onChangeFoo`
var handlerName = changeHandlerName(propName);
// handler encodes the value and updates the URL with the encoded value
// based on the `updateType` in the config. Default is `replaceIn`
handlersAccum[handlerName] = function generatedUrlChangeHandler(value) {
var location = _urlQueryConfig2.default.history.location;
// for backwards compatibility
if (!location) {
location = this.props.location;
}
var encodedValue = (0, _serialize.encode)(type, value);
// add a simple check when we have props.location.query to see if
// we even need to update.
if (location && location.query && location.query[queryParam] === encodedValue) {
return undefined; // skip updating
}
return (0, _updateUrlQuery.updateUrlQuerySingle)(updateType, queryParam, encodedValue, location);
}.bind(_this2); // bind this so we can access props dynamically
return handlersAccum;
}, {});
// add in a batch change handler
var batchHandlerName = changeHandlerName('urlQueryParams');
handlers[batchHandlerName] = function generatedBatchUrlChangeHandler(queryValues) {
var updateType = arguments.length <= 1 || arguments[1] === undefined ? _UrlUpdateTypes2.default.replaceIn : arguments[1];
var location = _urlQueryConfig2.default.history.location;
// for backwards compatibility
if (!location) {
location = this.props.location;
}
var allEncodedValuesUnchanged = true;
// encode each value
var queryReplacements = Object.keys(queryValues).reduce(function (accum, propName) {
var _urlPropsQueryConfig$3 = urlPropsQueryConfig[propName];
var _urlPropsQueryConfig$4 = _urlPropsQueryConfig$3.queryParam;
var queryParam = _urlPropsQueryConfig$4 === undefined ? propName : _urlPropsQueryConfig$4;
var type = _urlPropsQueryConfig$3.type;
var value = queryValues[propName];
var encodedValue = (0, _serialize.encode)(type, value);
accum[queryParam] = encodedValue;
// add a simple check when we have props.location.query to see if
// we even need to update.
if (location && location.query && location.query[queryParam] !== encodedValue) {
allEncodedValuesUnchanged = false;
}
return accum;
}, {});
if (location && location.query && allEncodedValuesUnchanged) {
return undefined; // skip updating if no encoded values changed
}
return (0, _updateUrlQuery.updateUrlQueryMulti)(updateType, queryReplacements, location);
}.bind(this);
// cache these so we don't regenerate new functions every render
cachedHandlers = handlers;
}
}
}
// if a manual mapping function is provided, use it, passing in the auto-generated
// handlers as an optional secondary argument.
if (mapUrlChangeHandlersToProps) {
handlers = mapUrlChangeHandlersToProps.call(this, propsWithUrl, handlers);
}
return handlers;
}
}, {
key: 'render',
value: function render() {
// get the url query parameters as an object mapping name to value.
// if a config is provided, these are decoded based on their `type` and their
// name will match the prop name.
// if no config is provided, they are not decoded and their names are whatever
// they were in the URL.
var url = getUrlObject(this.props);
// pass to mapUrlToProps for further decoding if provided
this.propsWithUrl = Object.assign({}, this.props, mapUrlToProps(url, this.props));
// add in the URL change handlers - either auto-generated based on config
// or from mapUrlChangeHandlersToProps.
Object.assign(this.propsWithUrl, this.getUrlChangeHandlerProps(this.propsWithUrl));
// render the wrapped component with the URL props added in.
return _react2.default.createElement(WrappedComponent, this.propsWithUrl);
}
}]);
return AddUrlProps;
}(_react.Component);
AddUrlProps.displayName = 'AddUrlProps(' + displayName + ')';
AddUrlProps.WrappedComponent = WrappedComponent;
AddUrlProps.propTypes = {
location: _propTypes2.default.any };
return AddUrlProps;
};
}