UNPKG

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.

278 lines (205 loc) 12.1 kB
'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; }; }(); 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; }; }