use-query-params
Version:
React Hook for managing state in URL query parameters with easy serialization.
632 lines (613 loc) • 20.7 kB
JavaScript
;
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