UNPKG

use-query-params

Version:

React Hook for managing state in URL query parameters with easy serialization.

632 lines (613 loc) 20.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { QueryParamProvider: () => QueryParamProvider, QueryParams: () => QueryParams, useQueryParam: () => useQueryParam, useQueryParams: () => useQueryParams, withQueryParams: () => withQueryParams, withQueryParamsMapped: () => withQueryParamsMapped }); module.exports = __toCommonJS(src_exports); __reExport(src_exports, require("serialize-query-params"), module.exports); // src/useQueryParam.ts var import_react2 = require("react"); // src/useQueryParams.ts var import_react = require("react"); var import_serialize_query_params4 = require("serialize-query-params"); // src/decodedParamCache.ts var DecodedParamCache = class { constructor() { this.paramsMap = /* @__PURE__ */ new Map(); this.registeredParams = /* @__PURE__ */ new Map(); } set(param, stringifiedValue, decodedValue, decode) { this.paramsMap.set(param, { stringified: stringifiedValue, decoded: decodedValue, decode }); } has(param, stringifiedValue, decode) { if (!this.paramsMap.has(param)) return false; const cachedParam = this.paramsMap.get(param); if (!cachedParam) return false; return cachedParam.stringified === stringifiedValue && (decode == null || cachedParam.decode === decode); } get(param) { var _a; if (this.paramsMap.has(param)) return (_a = this.paramsMap.get(param)) == null ? void 0 : _a.decoded; return void 0; } registerParams(paramNames) { for (const param of paramNames) { const currValue = this.registeredParams.get(param) || 0; this.registeredParams.set(param, currValue + 1); } } unregisterParams(paramNames) { for (const param of paramNames) { const value = (this.registeredParams.get(param) || 0) - 1; if (value <= 0) { this.registeredParams.delete(param); if (this.paramsMap.has(param)) { this.paramsMap.delete(param); } } else { this.registeredParams.set(param, value); } } } clear() { this.paramsMap.clear(); this.registeredParams.clear(); } }; var decodedParamCache = new DecodedParamCache(); // src/inheritedParams.ts var import_serialize_query_params = require("serialize-query-params"); function convertInheritedParamStringsToParams(paramConfigMapWithInherit, options) { var _a, _b, _c; const paramConfigMap = {}; let hasInherit = false; const hookKeys = Object.keys(paramConfigMapWithInherit); let paramKeys = hookKeys; const includeKnownParams = options.includeKnownParams || options.includeKnownParams !== false && hookKeys.length === 0; if (includeKnownParams) { const knownKeys = Object.keys((_a = options.params) != null ? _a : {}); paramKeys.push(...knownKeys); } for (const key of paramKeys) { const param = paramConfigMapWithInherit[key]; if (param != null && typeof param === "object") { paramConfigMap[key] = param; continue; } hasInherit = true; paramConfigMap[key] = (_c = (_b = options.params) == null ? void 0 : _b[key]) != null ? _c : import_serialize_query_params.StringParam; } if (!hasInherit) return paramConfigMapWithInherit; return paramConfigMap; } function extendParamConfigForKeys(baseParamConfigMap, paramKeys, inheritedParams, defaultParam) { var _a; if (!inheritedParams || !paramKeys.length) return baseParamConfigMap; let paramConfigMap = { ...baseParamConfigMap }; let hasInherit = false; for (const paramKey of paramKeys) { if (!Object.prototype.hasOwnProperty.call(paramConfigMap, paramKey)) { paramConfigMap[paramKey] = (_a = inheritedParams[paramKey]) != null ? _a : defaultParam; hasInherit = true; } } if (!hasInherit) return baseParamConfigMap; return paramConfigMap; } // src/shallowEqual.ts var hasOwnProperty = 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; } } function shallowEqual(objA, objB, equalMap) { var _a, _b; 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++) { const isEqual = (_b = (_a = equalMap == null ? void 0 : equalMap[keysA[i]]) == null ? void 0 : _a.equals) != null ? _b : is; if (!hasOwnProperty.call(objB, keysA[i]) || !isEqual(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; } // src/latestValues.ts function getLatestDecodedValues(parsedParams, paramConfigMap, decodedParamCache2) { const decodedValues = {}; const paramNames = Object.keys(paramConfigMap); for (const paramName of paramNames) { const paramConfig = paramConfigMap[paramName]; const encodedValue = parsedParams[paramName]; let decodedValue; if (decodedParamCache2.has(paramName, encodedValue, paramConfig.decode)) { decodedValue = decodedParamCache2.get(paramName); } else { decodedValue = paramConfig.decode(encodedValue); if (paramConfig.equals && decodedParamCache2.has(paramName, encodedValue)) { const oldDecodedValue = decodedParamCache2.get(paramName); if (paramConfig.equals(decodedValue, oldDecodedValue)) { decodedValue = oldDecodedValue; } } if (decodedValue !== void 0) { decodedParamCache2.set( paramName, encodedValue, decodedValue, paramConfig.decode ); } } if (decodedValue === void 0 && paramConfig.default !== void 0) { decodedValue = paramConfig.default; } decodedValues[paramName] = decodedValue; } return decodedValues; } function makeStableGetLatestDecodedValues() { let prevDecodedValues; function stableGetLatest(parsedParams, paramConfigMap, decodedParamCache2) { const decodedValues = getLatestDecodedValues( parsedParams, paramConfigMap, decodedParamCache2 ); if (prevDecodedValues != null && shallowEqual(prevDecodedValues, decodedValues)) { return prevDecodedValues; } prevDecodedValues = decodedValues; return decodedValues; } return stableGetLatest; } // src/urlName.ts function serializeUrlNameMap(paramConfigMap) { let urlNameMapParts; for (const paramName in paramConfigMap) { if (paramConfigMap[paramName].urlName) { const urlName = paramConfigMap[paramName].urlName; const part = `${urlName}\0${paramName}`; if (!urlNameMapParts) urlNameMapParts = [part]; else urlNameMapParts.push(part); } } return urlNameMapParts ? urlNameMapParts.join("\n") : void 0; } function deserializeUrlNameMap(urlNameMapStr) { if (!urlNameMapStr) return void 0; return Object.fromEntries( urlNameMapStr.split("\n").map((part) => part.split("\0")) ); } function applyUrlNames(encodedValues, paramConfigMap) { var _a; let newEncodedValues = {}; for (const paramName in encodedValues) { if (((_a = paramConfigMap[paramName]) == null ? void 0 : _a.urlName) != null) { newEncodedValues[paramConfigMap[paramName].urlName] = encodedValues[paramName]; } else { newEncodedValues[paramName] = encodedValues[paramName]; } } return newEncodedValues; } // src/memoSearchStringToObject.ts var cachedSearchString; var cachedUrlNameMapString; var cachedSearchStringToObjectFn; var cachedParsedQuery = {}; var memoSearchStringToObject = (searchStringToObject2, searchString, urlNameMapStr) => { if (cachedSearchString === searchString && cachedSearchStringToObjectFn === searchStringToObject2 && cachedUrlNameMapString === urlNameMapStr) { return cachedParsedQuery; } cachedSearchString = searchString; cachedSearchStringToObjectFn = searchStringToObject2; const newParsedQuery = searchStringToObject2(searchString != null ? searchString : ""); cachedUrlNameMapString = urlNameMapStr; const urlNameMap = deserializeUrlNameMap(urlNameMapStr); for (let [key, value] of Object.entries(newParsedQuery)) { if (urlNameMap == null ? void 0 : urlNameMap[key]) { delete newParsedQuery[key]; key = urlNameMap[key]; newParsedQuery[key] = value; } const oldValue = cachedParsedQuery[key]; if (shallowEqual(value, oldValue)) { newParsedQuery[key] = oldValue; } } cachedParsedQuery = newParsedQuery; return newParsedQuery; }; // src/options.ts var import_serialize_query_params2 = require("serialize-query-params"); var defaultOptions = { searchStringToObject: import_serialize_query_params2.searchStringToObject, objectToSearchString: import_serialize_query_params2.objectToSearchString, updateType: "pushIn", includeKnownParams: void 0, includeAllParams: false, removeDefaultsFromUrl: false, enableBatching: false, skipUpdateWhenNoChange: true }; function mergeOptions(parentOptions, currOptions) { if (currOptions == null) { currOptions = {}; } const merged = { ...parentOptions, ...currOptions }; if (currOptions.params && parentOptions.params) { merged.params = { ...parentOptions.params, ...currOptions.params }; } return merged; } // src/QueryParamProvider.tsx var React = __toESM(require("react")); var providerlessContextValue = { adapter: {}, options: defaultOptions }; var QueryParamContext = React.createContext( providerlessContextValue ); function useQueryParamContext() { const value = React.useContext(QueryParamContext); if (value === void 0 || value === providerlessContextValue) { throw new Error("useQueryParams must be used within a QueryParamProvider"); } return value; } function QueryParamProviderInner({ children, adapter, options }) { const { adapter: parentAdapter, options: parentOptions } = React.useContext(QueryParamContext); const value = React.useMemo(() => { return { adapter: adapter != null ? adapter : parentAdapter, options: mergeOptions( parentOptions, options ) }; }, [adapter, options, parentAdapter, parentOptions]); return /* @__PURE__ */ React.createElement(QueryParamContext.Provider, { value }, children); } function QueryParamProvider({ children, adapter, options }) { const Adapter = adapter; return Adapter ? /* @__PURE__ */ React.createElement(Adapter, null, (adapter2) => /* @__PURE__ */ React.createElement(QueryParamProviderInner, { adapter: adapter2, options }, children)) : /* @__PURE__ */ React.createElement(QueryParamProviderInner, { options }, children); } // src/updateSearchString.ts var import_serialize_query_params3 = require("serialize-query-params"); // src/removeDefaults.ts function removeDefaults(encodedValues, paramConfigMap) { var _a; for (const paramName in encodedValues) { if (((_a = paramConfigMap[paramName]) == null ? void 0 : _a.default) !== void 0 && encodedValues[paramName] !== void 0) { const encodedDefault = paramConfigMap[paramName].encode( paramConfigMap[paramName].default ); if (encodedDefault === encodedValues[paramName]) { encodedValues[paramName] = void 0; } } } } // src/updateSearchString.ts function getUpdatedSearchString({ changes, updateType, currentSearchString, paramConfigMap: baseParamConfigMap, options }) { const { searchStringToObject: searchStringToObject2, objectToSearchString: objectToSearchString2 } = options; if (updateType == null) updateType = options.updateType; let encodedChanges; const parsedParams = memoSearchStringToObject( searchStringToObject2, currentSearchString ); const paramConfigMap = extendParamConfigForKeys( baseParamConfigMap, Object.keys(changes), options.params ); let changesToUse; if (typeof changes === "function") { const latestValues = getLatestDecodedValues( parsedParams, paramConfigMap, decodedParamCache ); changesToUse = changes(latestValues); } else { changesToUse = changes; } encodedChanges = (0, import_serialize_query_params3.encodeQueryParams)(paramConfigMap, changesToUse); if (options.removeDefaultsFromUrl) { removeDefaults(encodedChanges, paramConfigMap); } encodedChanges = applyUrlNames(encodedChanges, paramConfigMap); let newSearchString; if (updateType === "push" || updateType === "replace") { newSearchString = objectToSearchString2(encodedChanges); } else { newSearchString = objectToSearchString2({ ...parsedParams, ...encodedChanges }); } if ((newSearchString == null ? void 0 : newSearchString.length) && newSearchString[0] !== "?") { newSearchString = `?${newSearchString}`; } return newSearchString != null ? newSearchString : ""; } function updateSearchString({ searchString, adapter, navigate, updateType }) { const currentLocation = adapter.location; const newLocation = { ...currentLocation, search: searchString }; if (navigate) { if (typeof updateType === "string" && updateType.startsWith("replace")) { adapter.replace(newLocation); } else { adapter.push(newLocation); } } } var immediateTask = (task) => task(); var timeoutTask = (task) => setTimeout(() => task(), 0); var updateQueue = []; function enqueueUpdate(args, { immediate } = {}) { updateQueue.push(args); let scheduleTask = immediate ? immediateTask : timeoutTask; if (updateQueue.length === 1) { scheduleTask(() => { const updates = updateQueue.slice(); updateQueue.length = 0; const initialSearchString = updates[0].currentSearchString; let searchString; for (let i = 0; i < updates.length; ++i) { const modifiedUpdate = i === 0 ? updates[i] : { ...updates[i], currentSearchString: searchString }; searchString = getUpdatedSearchString(modifiedUpdate); } if (args.options.skipUpdateWhenNoChange && searchString === initialSearchString) { return; } updateSearchString({ searchString: searchString != null ? searchString : "", adapter: updates[updates.length - 1].adapter, navigate: true, updateType: updates[updates.length - 1].updateType }); }); } } // src/useQueryParams.ts function useQueryParams(arg1, arg2) { const { adapter, options: contextOptions } = useQueryParamContext(); const [stableGetLatest] = (0, import_react.useState)(makeStableGetLatestDecodedValues); const { paramConfigMap: paramConfigMapWithInherit, options } = parseArguments( arg1, arg2 ); const mergedOptions = (0, import_react.useMemo)(() => { return mergeOptions(contextOptions, options); }, [contextOptions, options]); let paramConfigMap = convertInheritedParamStringsToParams( paramConfigMapWithInherit, mergedOptions ); const parsedParams = memoSearchStringToObject( mergedOptions.searchStringToObject, adapter.location.search, serializeUrlNameMap(paramConfigMap) ); if (mergedOptions.includeAllParams) { paramConfigMap = extendParamConfigForKeys( paramConfigMap, Object.keys(parsedParams), mergedOptions.params, import_serialize_query_params4.StringParam ); } const decodedValues = stableGetLatest( parsedParams, paramConfigMap, decodedParamCache ); const paramKeyString = Object.keys(paramConfigMap).join("\0"); (0, import_react.useEffect)(() => { const paramNames = paramKeyString.split("\0"); decodedParamCache.registerParams(paramNames); return () => { decodedParamCache.unregisterParams(paramNames); }; }, [paramKeyString]); const callbackDependencies = { adapter, paramConfigMap, options: mergedOptions }; const callbackDependenciesRef = (0, import_react.useRef)(callbackDependencies); if (callbackDependenciesRef.current == null) { callbackDependenciesRef.current = callbackDependencies; } (0, import_react.useEffect)(() => { callbackDependenciesRef.current.adapter = adapter; callbackDependenciesRef.current.paramConfigMap = paramConfigMap; callbackDependenciesRef.current.options = mergedOptions; }, [adapter, paramConfigMap, mergedOptions]); const [setQuery] = (0, import_react.useState)(() => { const setQuery2 = (changes, updateType) => { const { adapter: adapter2, paramConfigMap: paramConfigMap2, options: options2 } = callbackDependenciesRef.current; if (updateType == null) updateType = options2.updateType; enqueueUpdate( { changes, updateType, currentSearchString: adapter2.location.search, paramConfigMap: paramConfigMap2, options: options2, adapter: adapter2 }, { immediate: !options2.enableBatching } ); }; return setQuery2; }); return [decodedValues, setQuery]; } var useQueryParams_default = useQueryParams; function parseArguments(arg1, arg2) { let paramConfigMap; let options; if (arg1 === void 0) { paramConfigMap = {}; options = arg2; } else if (Array.isArray(arg1)) { paramConfigMap = Object.fromEntries( arg1.map((key) => [key, "inherit"]) ); options = arg2; } else { paramConfigMap = arg1; options = arg2; } return { paramConfigMap, options }; } // src/useQueryParam.ts var useQueryParam = (name, paramConfig, options) => { const paramConfigMap = (0, import_react2.useMemo)( () => ({ [name]: paramConfig != null ? paramConfig : "inherit" }), [name, paramConfig] ); const [query, setQuery] = useQueryParams_default(paramConfigMap, options); const decodedValue = query[name]; const setValue = (0, import_react2.useCallback)( (newValue, updateType) => { if (typeof newValue === "function") { return setQuery((latestValues) => { const newValueFromLatest = newValue(latestValues[name]); return { [name]: newValueFromLatest }; }, updateType); } return setQuery({ [name]: newValue }, updateType); }, [name, setQuery] ); return [decodedValue, setValue]; }; // src/withQueryParams.tsx var React2 = __toESM(require("react")); function withQueryParams(paramConfigMap, WrappedComponent) { const Component = (props) => { const [query, setQuery] = useQueryParams_default(paramConfigMap); return /* @__PURE__ */ React2.createElement(WrappedComponent, { query, setQuery, ...props }); }; Component.displayName = `withQueryParams(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`; return Component; } function withQueryParamsMapped(paramConfigMap, mapToProps, WrappedComponent) { const Component = (props) => { const [query, setQuery] = useQueryParams_default(paramConfigMap); const propsToAdd = mapToProps(query, setQuery, props); return /* @__PURE__ */ React2.createElement(WrappedComponent, { ...propsToAdd, ...props }); }; Component.displayName = `withQueryParams(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`; return Component; } // src/QueryParams.tsx var QueryParams = ({ config, children }) => { const [query, setQuery] = useQueryParams_default(config); return children({ query, setQuery }); }; //# sourceMappingURL=index.js.map