use-query-params
Advanced tools
Comparing version 0.6.0 to 1.0.0
@@ -5,5 +5,4 @@ export * from 'serialize-query-params'; | ||
export { useQueryParams } from './useQueryParams'; | ||
export { withQueryParams, InjectedQueryProps } from './withQueryParams'; | ||
export { withQueryParams, withQueryParamsMapped, InjectedQueryProps, } from './withQueryParams'; | ||
export { QueryParams, QueryParamsProps, QueryRenderProps } from './QueryParams'; | ||
export { updateUrlQuery } from './updateUrlQuery'; | ||
export { QueryParamProvider, QueryParamContext, QueryParamContextValue, } from './QueryParamProvider'; | ||
export { QueryParamProvider } from './QueryParamProvider'; |
export * from 'serialize-query-params'; | ||
export { useQueryParam } from './useQueryParam'; | ||
export { useQueryParams } from './useQueryParams'; | ||
export { withQueryParams } from './withQueryParams'; | ||
export { withQueryParams, withQueryParamsMapped, } from './withQueryParams'; | ||
export { QueryParams } from './QueryParams'; | ||
export { updateUrlQuery } from './updateUrlQuery'; | ||
export { QueryParamProvider, QueryParamContext, } from './QueryParamProvider'; | ||
export { QueryParamProvider } from './QueryParamProvider'; |
import * as React from 'react'; | ||
import { PushReplaceHistory } from './types'; | ||
import { HistoryLocation, PushReplaceHistory } from './types'; | ||
/** | ||
@@ -14,10 +14,6 @@ * Subset of a @reach/router history object. We only | ||
/** | ||
* Shape of the QueryParamContext, needed to update the URL | ||
* and know its current state. | ||
* Helper to produce the context value falling back to | ||
* window history and location if not provided. | ||
*/ | ||
export interface QueryParamContextValue { | ||
history: PushReplaceHistory; | ||
location: Location; | ||
} | ||
export declare const QueryParamContext: React.Context<QueryParamContextValue>; | ||
export declare function getLocationProps({ history, location, }?: Partial<HistoryLocation>): HistoryLocation; | ||
/** | ||
@@ -27,3 +23,3 @@ * Props for the Provider component, used to hook the active routing | ||
*/ | ||
interface Props { | ||
interface QueryParamProviderProps { | ||
/** Main app goes here */ | ||
@@ -47,3 +43,3 @@ children: React.ReactNode; | ||
*/ | ||
export declare function QueryParamProvider({ children, ReactRouterRoute, reachHistory, history, location, }: Props): JSX.Element; | ||
export declare function QueryParamProvider({ children, ReactRouterRoute, reachHistory, history, location, }: QueryParamProviderProps): JSX.Element; | ||
export default QueryParamProvider; |
@@ -13,2 +13,6 @@ var __assign = (this && this.__assign) || function () { | ||
import * as React from 'react'; | ||
import { LocationProvider } from './LocationProvider'; | ||
// we use a lazy caching solution to prevent #46 from happening | ||
var cachedWindowHistory; | ||
var cachedAdaptedWindowHistory; | ||
/** | ||
@@ -21,3 +25,6 @@ * Adapts standard DOM window history to work with our | ||
function adaptWindowHistory(history) { | ||
return { | ||
if (history === cachedWindowHistory && cachedAdaptedWindowHistory != null) { | ||
return cachedAdaptedWindowHistory; | ||
} | ||
var adaptedWindowHistory = { | ||
replace: function (location) { | ||
@@ -30,3 +37,9 @@ history.replaceState(location.state, '', location.protocol + "//" + location.host + location.pathname + location.search); | ||
}; | ||
cachedWindowHistory = history; | ||
cachedAdaptedWindowHistory = adaptedWindowHistory; | ||
return adaptedWindowHistory; | ||
} | ||
// we use a lazy caching solution to prevent #46 from happening | ||
var cachedReachHistory; | ||
var cachedAdaptedReachHistory; | ||
/** | ||
@@ -39,3 +52,6 @@ * Adapts @reach/router history to work with our | ||
function adaptReachHistory(history) { | ||
return { | ||
if (history === cachedReachHistory && cachedAdaptedReachHistory != null) { | ||
return cachedAdaptedReachHistory; | ||
} | ||
var adaptedReachHistory = { | ||
replace: function (location) { | ||
@@ -48,2 +64,5 @@ history.navigate(location.protocol + "//" + location.host + location.pathname + location.search, { replace: true }); | ||
}; | ||
cachedReachHistory = history; | ||
cachedAdaptedReachHistory = adaptedReachHistory; | ||
return adaptedReachHistory; | ||
} | ||
@@ -54,17 +73,18 @@ /** | ||
*/ | ||
function getContextValue(contextValue) { | ||
if (contextValue === void 0) { contextValue = {}; } | ||
var value = __assign({}, contextValue); | ||
export function getLocationProps(_a) { | ||
var _b = _a === void 0 ? {} : _a, history = _b.history, location = _b.location; | ||
var hasWindow = typeof window !== 'undefined'; | ||
if (hasWindow) { | ||
if (!value.history) { | ||
value.history = adaptWindowHistory(window.history); | ||
if (!history) { | ||
history = adaptWindowHistory(window.history); | ||
} | ||
if (!value.location) { | ||
value.location = window.location; | ||
if (!location) { | ||
location = window.location; | ||
} | ||
} | ||
return value; | ||
if (!location) { | ||
throw new Error("\n Could not read the location. Is the router wired up correctly?\n "); | ||
} | ||
return { history: history, location: location }; | ||
} | ||
export var QueryParamContext = React.createContext(getContextValue()); | ||
/** | ||
@@ -79,3 +99,3 @@ * Context provider for query params to have access to the | ||
return (React.createElement(ReactRouterRoute, null, function (routeProps) { | ||
return (React.createElement(QueryParamContext.Provider, { value: getContextValue(routeProps) }, children)); | ||
return (React.createElement(LocationProvider, __assign({}, getLocationProps(routeProps)), children)); | ||
})); | ||
@@ -85,10 +105,10 @@ } | ||
if (reachHistory) { | ||
return (React.createElement(QueryParamContext.Provider, { value: getContextValue({ | ||
history: adaptReachHistory(reachHistory), | ||
location: location, | ||
}) }, children)); | ||
return (React.createElement(LocationProvider, __assign({}, getLocationProps({ | ||
history: adaptReachHistory(reachHistory), | ||
location: location, | ||
})), children)); | ||
} | ||
// neither reach nor react-router, so allow manual overrides | ||
return (React.createElement(QueryParamContext.Provider, { value: getContextValue({ history: history, location: location }) }, children)); | ||
return (React.createElement(LocationProvider, __assign({}, getLocationProps({ history: history, location: location })), children)); | ||
} | ||
export default QueryParamProvider; |
@@ -18,3 +18,2 @@ import { QueryParamConfigMap, DecodedValueMap } from 'serialize-query-params'; | ||
replace: (location: Location) => void; | ||
location?: Location; | ||
} | ||
@@ -25,1 +24,10 @@ /** | ||
export declare type SetQuery<QPCMap extends QueryParamConfigMap> = (changes: Partial<DecodedValueMap<QPCMap>>, updateType?: UrlUpdateType) => void; | ||
export interface HistoryLocation { | ||
/** | ||
* History that meets the { replace, push } interface. | ||
* May be missing when run server-side. | ||
*/ | ||
history?: PushReplaceHistory; | ||
/** The location object */ | ||
location: Location; | ||
} |
@@ -1,10 +0,14 @@ | ||
import { EncodedQueryWithNulls } from 'serialize-query-params'; | ||
import { EncodedQuery } from 'serialize-query-params'; | ||
import { PushReplaceHistory, UrlUpdateType } from './types'; | ||
/** | ||
* Updates the URL to match the specified query changes. | ||
* Creates a new location object containing the specified query changes. | ||
* If replaceIn or pushIn are used as the updateType, then parameters | ||
* not specified in queryReplacements are retained. If replace or push | ||
* are used, only the values in queryReplacements will be available. | ||
* The default is pushIn. | ||
*/ | ||
export declare function updateUrlQuery(queryReplacements: EncodedQueryWithNulls, location: Location, history: PushReplaceHistory, updateType?: UrlUpdateType): void; | ||
export default updateUrlQuery; | ||
export declare function createLocationWithChanges(queryReplacements: EncodedQuery, location: Location, updateType?: UrlUpdateType): Location; | ||
/** | ||
* Updates the URL to the new location. | ||
*/ | ||
export declare function updateUrlQuery(history: PushReplaceHistory, location: Location, updateType?: UrlUpdateType): void; |
import { updateLocation, updateInLocation, } from 'serialize-query-params'; | ||
/** | ||
* Updates the URL to match the specified query changes. | ||
* Creates a new location object containing the specified query changes. | ||
* If replaceIn or pushIn are used as the updateType, then parameters | ||
* not specified in queryReplacements are retained. If replace or push | ||
* are used, only the values in queryReplacements will be available. | ||
* The default is pushIn. | ||
*/ | ||
export function updateUrlQuery(queryReplacements, location, history, updateType) { | ||
if (updateType === void 0) { updateType = 'replaceIn'; } | ||
export function createLocationWithChanges(queryReplacements, location, updateType) { | ||
if (updateType === void 0) { updateType = 'pushIn'; } | ||
switch (updateType) { | ||
case 'replace': | ||
case 'push': | ||
return updateLocation(queryReplacements, location); | ||
case 'replaceIn': | ||
history.replace(updateInLocation(queryReplacements, location)); | ||
break; | ||
case 'pushIn': | ||
history.push(updateInLocation(queryReplacements, location)); | ||
default: | ||
return updateInLocation(queryReplacements, location); | ||
} | ||
} | ||
/** | ||
* Updates the URL to the new location. | ||
*/ | ||
export function updateUrlQuery(history, location, updateType) { | ||
if (updateType === void 0) { updateType = 'pushIn'; } | ||
switch (updateType) { | ||
case 'pushIn': | ||
case 'push': | ||
history.push(location); | ||
break; | ||
case 'replaceIn': | ||
case 'replace': | ||
history.replace(updateLocation(queryReplacements, location)); | ||
default: | ||
history.replace(location); | ||
break; | ||
case 'push': | ||
history.push(updateLocation(queryReplacements, location)); | ||
break; | ||
default: | ||
} | ||
} | ||
export default updateUrlQuery; |
@@ -1,2 +0,2 @@ | ||
import { EncodedQueryWithNulls, QueryParamConfig } from 'serialize-query-params'; | ||
import { QueryParamConfig } from 'serialize-query-params'; | ||
/** | ||
@@ -8,6 +8,6 @@ * Given a query param name and query parameter configuration ({ encode, decode }) | ||
* is one of 'replace' | 'replaceIn' | 'push' | 'pushIn', defaulting to | ||
* 'replaceIn'. | ||
* 'pushIn'. | ||
* | ||
* You may optionally pass in a rawQuery object, otherwise the query is derived | ||
* from the location available in the QueryParamContext. | ||
* from the location available in the context. | ||
* | ||
@@ -17,2 +17,2 @@ * D = decoded type | ||
*/ | ||
export declare const useQueryParam: <D, D2 = D>(name: string, paramConfig?: QueryParamConfig<D, D2>, rawQuery?: EncodedQueryWithNulls | undefined) => [D2 | undefined, (newValue: D, updateType?: "replace" | "push" | "replaceIn" | "pushIn" | undefined) => void]; | ||
export declare const useQueryParam: <D, D2 = D>(name: string, paramConfig?: QueryParamConfig<D, D2>) => [D2 | undefined, (newValue: D, updateType?: "replace" | "push" | "replaceIn" | "pushIn" | undefined) => void]; |
import * as React from 'react'; | ||
import { parse as parseQueryString, parseUrl as parseQueryURL, stringify, StringParam, } from 'serialize-query-params'; | ||
import { QueryParamContext } from './QueryParamProvider'; | ||
import { updateUrlQuery } from './updateUrlQuery'; | ||
import { StringParam } from 'serialize-query-params'; | ||
import { getSSRSafeSearchString, usePreviousIfShallowEqual } from './helpers'; | ||
import { useLocationContext } from './LocationContext'; | ||
import { sharedMemoizedQueryParser } from './memoizedQueryParser'; | ||
/** | ||
@@ -11,6 +12,6 @@ * Given a query param name and query parameter configuration ({ encode, decode }) | ||
* is one of 'replace' | 'replaceIn' | 'push' | 'pushIn', defaulting to | ||
* 'replaceIn'. | ||
* 'pushIn'. | ||
* | ||
* You may optionally pass in a rawQuery object, otherwise the query is derived | ||
* from the location available in the QueryParamContext. | ||
* from the location available in the context. | ||
* | ||
@@ -20,66 +21,23 @@ * D = decoded type | ||
*/ | ||
export var useQueryParam = function (name, paramConfig, rawQuery) { | ||
var _a; | ||
export var useQueryParam = function (name, paramConfig) { | ||
if (paramConfig === void 0) { paramConfig = StringParam; } | ||
var _b = React.useContext(QueryParamContext), history = _b.history, location = _b.location; | ||
// ref with current version history object (see #46) | ||
var refHistory = React.useRef(history); | ||
React.useEffect(function () { | ||
refHistory.current = history; | ||
}, [history]); | ||
// ref with current version location object (see #46) | ||
var refLocation = React.useRef(location); | ||
React.useEffect(function () { | ||
refLocation.current = location; | ||
}, [location]); | ||
var _a = useLocationContext(), location = _a[0], setLocation = _a[1]; | ||
// create the setter, memoizing via useCallback | ||
var setValue = React.useCallback(function (newValue, updateType) { | ||
var _a; | ||
var newEncodedValue = paramConfig.encode(newValue); | ||
setLocation((_a = {}, _a[name] = newEncodedValue, _a), updateType); | ||
}, [paramConfig, name, setLocation]); | ||
// read in the raw query | ||
if (!rawQuery) { | ||
var locationIsObject_1 = typeof location === 'object'; | ||
var windowIsDefined_1 = typeof window !== 'undefined'; | ||
rawQuery = React.useMemo(function () { | ||
var pathname = {}; | ||
// handle checking SSR (#13) | ||
if (locationIsObject_1) { | ||
// in browser | ||
if (windowIsDefined_1) { | ||
pathname = parseQueryString(location.search); | ||
} | ||
else { | ||
// not in browser | ||
var url = location.pathname; | ||
if (location.search) { | ||
url += location.search; | ||
} | ||
pathname = parseQueryURL(url).query; | ||
} | ||
} | ||
return pathname || {}; | ||
}, [location.search, location.pathname, locationIsObject_1, windowIsDefined_1]); | ||
} | ||
// read in the encoded string value | ||
var encodedValue = rawQuery[name]; | ||
// note that we use the stringified encoded value since the encoded | ||
// value may be an array that is recreated if a different query param | ||
// changes. It is sufficient to use this instead of encodedValue in | ||
// the useMemo dependency array since it will change any time the actual | ||
// meaningful value of encodedValue changes. | ||
var arraySafeEncodedValue = encodedValue instanceof Array | ||
? stringify((_a = {}, _a[name] = encodedValue, _a)) | ||
: encodedValue; | ||
var parsedQuery = sharedMemoizedQueryParser(getSSRSafeSearchString(location)); | ||
// read in the encoded string value (we have to use previous if available because | ||
// sometimes the query string changes so we get a new parsedQuery but this value | ||
// didn't change, so we should avoid generating a new array or whatever value) | ||
var encodedValue = usePreviousIfShallowEqual(parsedQuery[name]); | ||
// decode if the encoded value has changed, otherwise | ||
// re-use memoized value | ||
var decodedValue = React.useMemo(function () { | ||
if (encodedValue == null) { | ||
return undefined; | ||
} | ||
return paramConfig.decode(encodedValue); | ||
}, [arraySafeEncodedValue, paramConfig]); // eslint-disable-line react-hooks/exhaustive-deps | ||
// create the setter, memoizing via useCallback | ||
var setValue = React.useCallback(function (newValue, updateType) { | ||
var _a; | ||
var newEncodedValue = paramConfig.encode(newValue); | ||
updateUrlQuery((_a = {}, _a[name] = newEncodedValue, _a), refHistory.current.location || refLocation.current, // see #46 for why we use a ref here | ||
refHistory.current, updateType); | ||
}, [paramConfig, name]); | ||
}, [encodedValue, paramConfig]); | ||
return [decodedValue, setValue]; | ||
}; |
import * as React from 'react'; | ||
import { parse as parseQueryString, encodeQueryParams, } from 'serialize-query-params'; | ||
import { useQueryParam } from './useQueryParam'; | ||
import updateUrlQuery from './updateUrlQuery'; | ||
import { QueryParamContext } from './QueryParamProvider'; | ||
// from https://usehooks.com/usePrevious/ | ||
function usePrevious(value) { | ||
var ref = React.useRef(value); | ||
React.useEffect(function () { | ||
ref.current = value; | ||
}, [value]); | ||
return ref.current; | ||
} | ||
// from https://github.com/lodash/lodash/issues/2340#issuecomment-360325395 | ||
function isShallowEqual(objA, objB) { | ||
for (var key in objA) | ||
if (!(key in objB) || objA[key] !== objB[key]) | ||
return false; | ||
for (var key in objB) | ||
if (!(key in objA) || objA[key] !== objB[key]) | ||
return false; | ||
return true; | ||
} | ||
import { encodeQueryParams, } from 'serialize-query-params'; | ||
import { getSSRSafeSearchString, usePreviousIfShallowEqual } from './helpers'; | ||
import { useLocationContext } from './LocationContext'; | ||
import { sharedMemoizedQueryParser } from './memoizedQueryParser'; | ||
import shallowEqual from './shallowEqual'; | ||
/** | ||
@@ -29,52 +12,46 @@ * Given a query parameter configuration (mapping query param name to { encode, decode }), | ||
export var useQueryParams = function (paramConfigMap) { | ||
var _a = React.useContext(QueryParamContext), history = _a.history, location = _a.location; | ||
var locationIsObject = typeof location === 'object'; | ||
var _a = useLocationContext(), location = _a[0], setLocation = _a[1]; | ||
var decodedValueCacheRef = React.useRef({}); | ||
// memoize paramConfigMap to make the API nicer for consumers. | ||
// otherwise we'd have to useQueryParams(useMemo(() => { foo: NumberParam }, [])) | ||
var prevParamConfigMap = usePrevious(paramConfigMap); | ||
var hasNewParamConfig = isShallowEqual(prevParamConfigMap, paramConfigMap); | ||
// prettier-ignore | ||
var memoParamConfigMap = React.useMemo(function () { return paramConfigMap; }, [ | ||
hasNewParamConfig, | ||
]); | ||
paramConfigMap = memoParamConfigMap; | ||
// ref with current version history object (see #46) | ||
var refHistory = React.useRef(history); | ||
React.useEffect(function () { | ||
refHistory.current = history; | ||
}, [history]); | ||
// ref with current version location object (see #46) | ||
var refLocation = React.useRef(location); | ||
React.useEffect(function () { | ||
refLocation.current = location; | ||
}, [location]); | ||
var search = locationIsObject ? location.search : ''; | ||
paramConfigMap = usePreviousIfShallowEqual(paramConfigMap); | ||
// create a setter for updating multiple query params at once | ||
var setQuery = React.useCallback(function (changes, updateType) { | ||
// encode as strings for the URL | ||
var encodedChanges = encodeQueryParams(paramConfigMap, changes); | ||
// update the URL | ||
setLocation(encodedChanges, updateType); | ||
}, [paramConfigMap, setLocation]); | ||
// read in the raw query | ||
var rawQuery = React.useMemo(function () { return parseQueryString(search) || {}; }, [ | ||
search, | ||
]); | ||
// parse each parameter via useQueryParam | ||
// we reuse the logic to not recreate objects | ||
var paramNames = Object.keys(paramConfigMap); | ||
var paramValues = paramNames.map(function (paramName) { | ||
return useQueryParam(paramName, paramConfigMap[paramName], rawQuery)[0]; | ||
}); | ||
// we use a memo here to prevent recreating the containing decodedValues object | ||
// which would break === comparisons even if no values changed. | ||
var parsedQuery = sharedMemoizedQueryParser(getSSRSafeSearchString(location)); | ||
// decode all the values if we have changes | ||
var decodedValues = React.useMemo(function () { | ||
// iterate over the decoded values and build an object | ||
var decodedValueCache = decodedValueCacheRef.current; | ||
// we are decoding since the parsed query changed or the param config map has changed. | ||
// recompute new values but only for those that changed | ||
var paramNames = Object.keys(paramConfigMap); | ||
var decodedValues = {}; | ||
for (var i = 0; i < paramNames.length; ++i) { | ||
decodedValues[paramNames[i]] = paramValues[i]; | ||
for (var _i = 0, paramNames_1 = paramNames; _i < paramNames_1.length; _i++) { | ||
var paramName = paramNames_1[_i]; | ||
var paramConfig = paramConfigMap[paramName]; | ||
var encodedValue = parsedQuery[paramName]; | ||
var decodedValue = void 0; | ||
// re-use cached decoded value if we can, otherwise generate new | ||
var cacheEntry = decodedValueCache[paramName]; | ||
if (cacheEntry != undefined && | ||
shallowEqual(cacheEntry.encodedValue, encodedValue)) { | ||
decodedValue = cacheEntry.decodedValue; | ||
} | ||
else { | ||
if (cacheEntry == null) { | ||
decodedValueCache[paramName] = {}; | ||
} | ||
decodedValue = paramConfig.decode(encodedValue); | ||
decodedValueCache[paramName].encodedValue = encodedValue; | ||
decodedValueCache[paramName].decodedValue = decodedValue; | ||
} | ||
decodedValues[paramName] = decodedValue; | ||
} | ||
return decodedValues; | ||
}, paramValues); // eslint-disable-line react-hooks/exhaustive-deps | ||
// create a setter for updating multiple query params at once | ||
var setQuery = React.useCallback(function (changes, updateType) { | ||
// encode as strings for the URL | ||
var encodedChanges = encodeQueryParams(paramConfigMap, changes); | ||
// update the URL | ||
updateUrlQuery(encodedChanges, refHistory.current.location || refLocation.current, // see #46 | ||
refHistory.current, updateType); | ||
}, [paramConfigMap]); | ||
}, [paramConfigMap, parsedQuery]); | ||
// no longer Partial | ||
@@ -81,0 +58,0 @@ return [decodedValues, setQuery]; |
import * as React from 'react'; | ||
import { QueryParamConfigMap, DecodedValueMap } from 'serialize-query-params'; | ||
import { SetQuery } from './types'; | ||
declare type Omit<T, K> = Pick<T, Exclude<keyof T, K>>; | ||
declare type Diff<T, K> = Omit<T, keyof K>; | ||
export interface InjectedQueryProps<QPCMap extends QueryParamConfigMap> { | ||
@@ -15,3 +17,12 @@ query: DecodedValueMap<QPCMap>; | ||
*/ | ||
export declare function withQueryParams<QPCMap extends QueryParamConfigMap, P extends InjectedQueryProps<QPCMap>>(paramConfigMap: QPCMap, WrappedComponent: React.ComponentType<P>): React.FunctionComponent<Pick<P, Exclude<keyof P, "query" | "setQuery">>>; | ||
export declare function withQueryParams<QPCMap extends QueryParamConfigMap, P extends InjectedQueryProps<QPCMap>>(paramConfigMap: QPCMap, WrappedComponent: React.ComponentType<P>): React.FC<Pick<P, Exclude<keyof P, "query" | "setQuery">>>; | ||
export default withQueryParams; | ||
/** | ||
* HOC to provide query parameters via props mapToProps (similar to | ||
* react-redux connect style mapStateToProps) | ||
* NOTE: I couldn't get type to automatically infer generic when | ||
* using the format withQueryParams(config)(component), so I switched | ||
* to withQueryParams(config, component). | ||
* See: https://github.com/microsoft/TypeScript/issues/30134 | ||
*/ | ||
export declare function withQueryParamsMapped<QPCMap extends QueryParamConfigMap, MappedProps extends object, P extends MappedProps>(paramConfigMap: QPCMap, mapToProps: (query: DecodedValueMap<QPCMap>, setQuery: SetQuery<QPCMap>, props: Diff<P, MappedProps>) => MappedProps, WrappedComponent: React.ComponentType<P>): React.FC<Pick<P, Exclude<keyof P, keyof MappedProps>>>; |
@@ -28,7 +28,24 @@ var __assign = (this && this.__assign) || function () { | ||
}; | ||
Component.displayName = "withQueryParams(" + (WrappedComponent.displayName || | ||
WrappedComponent.name || | ||
'Component') + ")"; | ||
Component.displayName = "withQueryParams(" + (WrappedComponent.displayName || WrappedComponent.name || 'Component') + ")"; | ||
return Component; | ||
} | ||
export default withQueryParams; | ||
/** | ||
* HOC to provide query parameters via props mapToProps (similar to | ||
* react-redux connect style mapStateToProps) | ||
* NOTE: I couldn't get type to automatically infer generic when | ||
* using the format withQueryParams(config)(component), so I switched | ||
* to withQueryParams(config, component). | ||
* See: https://github.com/microsoft/TypeScript/issues/30134 | ||
*/ | ||
export function withQueryParamsMapped(paramConfigMap, mapToProps, WrappedComponent) { | ||
// return a FC that takes props excluding query and setQuery | ||
var Component = function (props) { | ||
var _a = useQueryParams(paramConfigMap), query = _a[0], setQuery = _a[1]; | ||
var propsToAdd = mapToProps(query, setQuery, props); | ||
// see https://github.com/microsoft/TypeScript/issues/28938#issuecomment-450636046 for why `...props as P` | ||
return React.createElement(WrappedComponent, __assign({}, propsToAdd, props)); | ||
}; | ||
Component.displayName = "withQueryParams(" + (WrappedComponent.displayName || WrappedComponent.name || 'Component') + ")"; | ||
return Component; | ||
} |
@@ -5,5 +5,4 @@ export * from 'serialize-query-params'; | ||
export { useQueryParams } from './useQueryParams'; | ||
export { withQueryParams, InjectedQueryProps } from './withQueryParams'; | ||
export { withQueryParams, withQueryParamsMapped, InjectedQueryProps, } from './withQueryParams'; | ||
export { QueryParams, QueryParamsProps, QueryRenderProps } from './QueryParams'; | ||
export { updateUrlQuery } from './updateUrlQuery'; | ||
export { QueryParamProvider, QueryParamContext, QueryParamContextValue, } from './QueryParamProvider'; | ||
export { QueryParamProvider } from './QueryParamProvider'; |
@@ -13,8 +13,6 @@ "use strict"; | ||
exports.withQueryParams = withQueryParams_1.withQueryParams; | ||
exports.withQueryParamsMapped = withQueryParams_1.withQueryParamsMapped; | ||
var QueryParams_1 = require("./QueryParams"); | ||
exports.QueryParams = QueryParams_1.QueryParams; | ||
var updateUrlQuery_1 = require("./updateUrlQuery"); | ||
exports.updateUrlQuery = updateUrlQuery_1.updateUrlQuery; | ||
var QueryParamProvider_1 = require("./QueryParamProvider"); | ||
exports.QueryParamProvider = QueryParamProvider_1.QueryParamProvider; | ||
exports.QueryParamContext = QueryParamProvider_1.QueryParamContext; |
import * as React from 'react'; | ||
import { PushReplaceHistory } from './types'; | ||
import { HistoryLocation, PushReplaceHistory } from './types'; | ||
/** | ||
@@ -14,10 +14,6 @@ * Subset of a @reach/router history object. We only | ||
/** | ||
* Shape of the QueryParamContext, needed to update the URL | ||
* and know its current state. | ||
* Helper to produce the context value falling back to | ||
* window history and location if not provided. | ||
*/ | ||
export interface QueryParamContextValue { | ||
history: PushReplaceHistory; | ||
location: Location; | ||
} | ||
export declare const QueryParamContext: React.Context<QueryParamContextValue>; | ||
export declare function getLocationProps({ history, location, }?: Partial<HistoryLocation>): HistoryLocation; | ||
/** | ||
@@ -27,3 +23,3 @@ * Props for the Provider component, used to hook the active routing | ||
*/ | ||
interface Props { | ||
interface QueryParamProviderProps { | ||
/** Main app goes here */ | ||
@@ -47,3 +43,3 @@ children: React.ReactNode; | ||
*/ | ||
export declare function QueryParamProvider({ children, ReactRouterRoute, reachHistory, history, location, }: Props): JSX.Element; | ||
export declare function QueryParamProvider({ children, ReactRouterRoute, reachHistory, history, location, }: QueryParamProviderProps): JSX.Element; | ||
export default QueryParamProvider; |
@@ -15,2 +15,6 @@ "use strict"; | ||
var React = require("react"); | ||
var LocationProvider_1 = require("./LocationProvider"); | ||
// we use a lazy caching solution to prevent #46 from happening | ||
var cachedWindowHistory; | ||
var cachedAdaptedWindowHistory; | ||
/** | ||
@@ -23,3 +27,6 @@ * Adapts standard DOM window history to work with our | ||
function adaptWindowHistory(history) { | ||
return { | ||
if (history === cachedWindowHistory && cachedAdaptedWindowHistory != null) { | ||
return cachedAdaptedWindowHistory; | ||
} | ||
var adaptedWindowHistory = { | ||
replace: function (location) { | ||
@@ -32,3 +39,9 @@ history.replaceState(location.state, '', location.protocol + "//" + location.host + location.pathname + location.search); | ||
}; | ||
cachedWindowHistory = history; | ||
cachedAdaptedWindowHistory = adaptedWindowHistory; | ||
return adaptedWindowHistory; | ||
} | ||
// we use a lazy caching solution to prevent #46 from happening | ||
var cachedReachHistory; | ||
var cachedAdaptedReachHistory; | ||
/** | ||
@@ -41,3 +54,6 @@ * Adapts @reach/router history to work with our | ||
function adaptReachHistory(history) { | ||
return { | ||
if (history === cachedReachHistory && cachedAdaptedReachHistory != null) { | ||
return cachedAdaptedReachHistory; | ||
} | ||
var adaptedReachHistory = { | ||
replace: function (location) { | ||
@@ -50,2 +66,5 @@ history.navigate(location.protocol + "//" + location.host + location.pathname + location.search, { replace: true }); | ||
}; | ||
cachedReachHistory = history; | ||
cachedAdaptedReachHistory = adaptedReachHistory; | ||
return adaptedReachHistory; | ||
} | ||
@@ -56,17 +75,19 @@ /** | ||
*/ | ||
function getContextValue(contextValue) { | ||
if (contextValue === void 0) { contextValue = {}; } | ||
var value = __assign({}, contextValue); | ||
function getLocationProps(_a) { | ||
var _b = _a === void 0 ? {} : _a, history = _b.history, location = _b.location; | ||
var hasWindow = typeof window !== 'undefined'; | ||
if (hasWindow) { | ||
if (!value.history) { | ||
value.history = adaptWindowHistory(window.history); | ||
if (!history) { | ||
history = adaptWindowHistory(window.history); | ||
} | ||
if (!value.location) { | ||
value.location = window.location; | ||
if (!location) { | ||
location = window.location; | ||
} | ||
} | ||
return value; | ||
if (!location) { | ||
throw new Error("\n Could not read the location. Is the router wired up correctly?\n "); | ||
} | ||
return { history: history, location: location }; | ||
} | ||
exports.QueryParamContext = React.createContext(getContextValue()); | ||
exports.getLocationProps = getLocationProps; | ||
/** | ||
@@ -81,3 +102,3 @@ * Context provider for query params to have access to the | ||
return (React.createElement(ReactRouterRoute, null, function (routeProps) { | ||
return (React.createElement(exports.QueryParamContext.Provider, { value: getContextValue(routeProps) }, children)); | ||
return (React.createElement(LocationProvider_1.LocationProvider, __assign({}, getLocationProps(routeProps)), children)); | ||
})); | ||
@@ -87,11 +108,11 @@ } | ||
if (reachHistory) { | ||
return (React.createElement(exports.QueryParamContext.Provider, { value: getContextValue({ | ||
history: adaptReachHistory(reachHistory), | ||
location: location, | ||
}) }, children)); | ||
return (React.createElement(LocationProvider_1.LocationProvider, __assign({}, getLocationProps({ | ||
history: adaptReachHistory(reachHistory), | ||
location: location, | ||
})), children)); | ||
} | ||
// neither reach nor react-router, so allow manual overrides | ||
return (React.createElement(exports.QueryParamContext.Provider, { value: getContextValue({ history: history, location: location }) }, children)); | ||
return (React.createElement(LocationProvider_1.LocationProvider, __assign({}, getLocationProps({ history: history, location: location })), children)); | ||
} | ||
exports.QueryParamProvider = QueryParamProvider; | ||
exports.default = QueryParamProvider; |
@@ -18,3 +18,2 @@ import { QueryParamConfigMap, DecodedValueMap } from 'serialize-query-params'; | ||
replace: (location: Location) => void; | ||
location?: Location; | ||
} | ||
@@ -25,1 +24,10 @@ /** | ||
export declare type SetQuery<QPCMap extends QueryParamConfigMap> = (changes: Partial<DecodedValueMap<QPCMap>>, updateType?: UrlUpdateType) => void; | ||
export interface HistoryLocation { | ||
/** | ||
* History that meets the { replace, push } interface. | ||
* May be missing when run server-side. | ||
*/ | ||
history?: PushReplaceHistory; | ||
/** The location object */ | ||
location: Location; | ||
} |
@@ -1,10 +0,14 @@ | ||
import { EncodedQueryWithNulls } from 'serialize-query-params'; | ||
import { EncodedQuery } from 'serialize-query-params'; | ||
import { PushReplaceHistory, UrlUpdateType } from './types'; | ||
/** | ||
* Updates the URL to match the specified query changes. | ||
* Creates a new location object containing the specified query changes. | ||
* If replaceIn or pushIn are used as the updateType, then parameters | ||
* not specified in queryReplacements are retained. If replace or push | ||
* are used, only the values in queryReplacements will be available. | ||
* The default is pushIn. | ||
*/ | ||
export declare function updateUrlQuery(queryReplacements: EncodedQueryWithNulls, location: Location, history: PushReplaceHistory, updateType?: UrlUpdateType): void; | ||
export default updateUrlQuery; | ||
export declare function createLocationWithChanges(queryReplacements: EncodedQuery, location: Location, updateType?: UrlUpdateType): Location; | ||
/** | ||
* Updates the URL to the new location. | ||
*/ | ||
export declare function updateUrlQuery(history: PushReplaceHistory, location: Location, updateType?: UrlUpdateType): void; |
@@ -5,26 +5,38 @@ "use strict"; | ||
/** | ||
* Updates the URL to match the specified query changes. | ||
* Creates a new location object containing the specified query changes. | ||
* If replaceIn or pushIn are used as the updateType, then parameters | ||
* not specified in queryReplacements are retained. If replace or push | ||
* are used, only the values in queryReplacements will be available. | ||
* The default is pushIn. | ||
*/ | ||
function updateUrlQuery(queryReplacements, location, history, updateType) { | ||
if (updateType === void 0) { updateType = 'replaceIn'; } | ||
function createLocationWithChanges(queryReplacements, location, updateType) { | ||
if (updateType === void 0) { updateType = 'pushIn'; } | ||
switch (updateType) { | ||
case 'replace': | ||
case 'push': | ||
return serialize_query_params_1.updateLocation(queryReplacements, location); | ||
case 'replaceIn': | ||
history.replace(serialize_query_params_1.updateInLocation(queryReplacements, location)); | ||
break; | ||
case 'pushIn': | ||
history.push(serialize_query_params_1.updateInLocation(queryReplacements, location)); | ||
default: | ||
return serialize_query_params_1.updateInLocation(queryReplacements, location); | ||
} | ||
} | ||
exports.createLocationWithChanges = createLocationWithChanges; | ||
/** | ||
* Updates the URL to the new location. | ||
*/ | ||
function updateUrlQuery(history, location, updateType) { | ||
if (updateType === void 0) { updateType = 'pushIn'; } | ||
switch (updateType) { | ||
case 'pushIn': | ||
case 'push': | ||
history.push(location); | ||
break; | ||
case 'replaceIn': | ||
case 'replace': | ||
history.replace(serialize_query_params_1.updateLocation(queryReplacements, location)); | ||
default: | ||
history.replace(location); | ||
break; | ||
case 'push': | ||
history.push(serialize_query_params_1.updateLocation(queryReplacements, location)); | ||
break; | ||
default: | ||
} | ||
} | ||
exports.updateUrlQuery = updateUrlQuery; | ||
exports.default = updateUrlQuery; |
@@ -1,2 +0,2 @@ | ||
import { EncodedQueryWithNulls, QueryParamConfig } from 'serialize-query-params'; | ||
import { QueryParamConfig } from 'serialize-query-params'; | ||
/** | ||
@@ -8,6 +8,6 @@ * Given a query param name and query parameter configuration ({ encode, decode }) | ||
* is one of 'replace' | 'replaceIn' | 'push' | 'pushIn', defaulting to | ||
* 'replaceIn'. | ||
* 'pushIn'. | ||
* | ||
* You may optionally pass in a rawQuery object, otherwise the query is derived | ||
* from the location available in the QueryParamContext. | ||
* from the location available in the context. | ||
* | ||
@@ -17,2 +17,2 @@ * D = decoded type | ||
*/ | ||
export declare const useQueryParam: <D, D2 = D>(name: string, paramConfig?: QueryParamConfig<D, D2>, rawQuery?: EncodedQueryWithNulls | undefined) => [D2 | undefined, (newValue: D, updateType?: "replace" | "push" | "replaceIn" | "pushIn" | undefined) => void]; | ||
export declare const useQueryParam: <D, D2 = D>(name: string, paramConfig?: QueryParamConfig<D, D2>) => [D2 | undefined, (newValue: D, updateType?: "replace" | "push" | "replaceIn" | "pushIn" | undefined) => void]; |
@@ -5,4 +5,5 @@ "use strict"; | ||
var serialize_query_params_1 = require("serialize-query-params"); | ||
var QueryParamProvider_1 = require("./QueryParamProvider"); | ||
var updateUrlQuery_1 = require("./updateUrlQuery"); | ||
var helpers_1 = require("./helpers"); | ||
var LocationContext_1 = require("./LocationContext"); | ||
var memoizedQueryParser_1 = require("./memoizedQueryParser"); | ||
/** | ||
@@ -14,6 +15,6 @@ * Given a query param name and query parameter configuration ({ encode, decode }) | ||
* is one of 'replace' | 'replaceIn' | 'push' | 'pushIn', defaulting to | ||
* 'replaceIn'. | ||
* 'pushIn'. | ||
* | ||
* You may optionally pass in a rawQuery object, otherwise the query is derived | ||
* from the location available in the QueryParamContext. | ||
* from the location available in the context. | ||
* | ||
@@ -23,66 +24,23 @@ * D = decoded type | ||
*/ | ||
exports.useQueryParam = function (name, paramConfig, rawQuery) { | ||
var _a; | ||
exports.useQueryParam = function (name, paramConfig) { | ||
if (paramConfig === void 0) { paramConfig = serialize_query_params_1.StringParam; } | ||
var _b = React.useContext(QueryParamProvider_1.QueryParamContext), history = _b.history, location = _b.location; | ||
// ref with current version history object (see #46) | ||
var refHistory = React.useRef(history); | ||
React.useEffect(function () { | ||
refHistory.current = history; | ||
}, [history]); | ||
// ref with current version location object (see #46) | ||
var refLocation = React.useRef(location); | ||
React.useEffect(function () { | ||
refLocation.current = location; | ||
}, [location]); | ||
var _a = LocationContext_1.useLocationContext(), location = _a[0], setLocation = _a[1]; | ||
// create the setter, memoizing via useCallback | ||
var setValue = React.useCallback(function (newValue, updateType) { | ||
var _a; | ||
var newEncodedValue = paramConfig.encode(newValue); | ||
setLocation((_a = {}, _a[name] = newEncodedValue, _a), updateType); | ||
}, [paramConfig, name, setLocation]); | ||
// read in the raw query | ||
if (!rawQuery) { | ||
var locationIsObject_1 = typeof location === 'object'; | ||
var windowIsDefined_1 = typeof window !== 'undefined'; | ||
rawQuery = React.useMemo(function () { | ||
var pathname = {}; | ||
// handle checking SSR (#13) | ||
if (locationIsObject_1) { | ||
// in browser | ||
if (windowIsDefined_1) { | ||
pathname = serialize_query_params_1.parse(location.search); | ||
} | ||
else { | ||
// not in browser | ||
var url = location.pathname; | ||
if (location.search) { | ||
url += location.search; | ||
} | ||
pathname = serialize_query_params_1.parseUrl(url).query; | ||
} | ||
} | ||
return pathname || {}; | ||
}, [location.search, location.pathname, locationIsObject_1, windowIsDefined_1]); | ||
} | ||
// read in the encoded string value | ||
var encodedValue = rawQuery[name]; | ||
// note that we use the stringified encoded value since the encoded | ||
// value may be an array that is recreated if a different query param | ||
// changes. It is sufficient to use this instead of encodedValue in | ||
// the useMemo dependency array since it will change any time the actual | ||
// meaningful value of encodedValue changes. | ||
var arraySafeEncodedValue = encodedValue instanceof Array | ||
? serialize_query_params_1.stringify((_a = {}, _a[name] = encodedValue, _a)) | ||
: encodedValue; | ||
var parsedQuery = memoizedQueryParser_1.sharedMemoizedQueryParser(helpers_1.getSSRSafeSearchString(location)); | ||
// read in the encoded string value (we have to use previous if available because | ||
// sometimes the query string changes so we get a new parsedQuery but this value | ||
// didn't change, so we should avoid generating a new array or whatever value) | ||
var encodedValue = helpers_1.usePreviousIfShallowEqual(parsedQuery[name]); | ||
// decode if the encoded value has changed, otherwise | ||
// re-use memoized value | ||
var decodedValue = React.useMemo(function () { | ||
if (encodedValue == null) { | ||
return undefined; | ||
} | ||
return paramConfig.decode(encodedValue); | ||
}, [arraySafeEncodedValue, paramConfig]); // eslint-disable-line react-hooks/exhaustive-deps | ||
// create the setter, memoizing via useCallback | ||
var setValue = React.useCallback(function (newValue, updateType) { | ||
var _a; | ||
var newEncodedValue = paramConfig.encode(newValue); | ||
updateUrlQuery_1.updateUrlQuery((_a = {}, _a[name] = newEncodedValue, _a), refHistory.current.location || refLocation.current, // see #46 for why we use a ref here | ||
refHistory.current, updateType); | ||
}, [paramConfig, name]); | ||
}, [encodedValue, paramConfig]); | ||
return [decodedValue, setValue]; | ||
}; |
@@ -5,23 +5,6 @@ "use strict"; | ||
var serialize_query_params_1 = require("serialize-query-params"); | ||
var useQueryParam_1 = require("./useQueryParam"); | ||
var updateUrlQuery_1 = require("./updateUrlQuery"); | ||
var QueryParamProvider_1 = require("./QueryParamProvider"); | ||
// from https://usehooks.com/usePrevious/ | ||
function usePrevious(value) { | ||
var ref = React.useRef(value); | ||
React.useEffect(function () { | ||
ref.current = value; | ||
}, [value]); | ||
return ref.current; | ||
} | ||
// from https://github.com/lodash/lodash/issues/2340#issuecomment-360325395 | ||
function isShallowEqual(objA, objB) { | ||
for (var key in objA) | ||
if (!(key in objB) || objA[key] !== objB[key]) | ||
return false; | ||
for (var key in objB) | ||
if (!(key in objA) || objA[key] !== objB[key]) | ||
return false; | ||
return true; | ||
} | ||
var helpers_1 = require("./helpers"); | ||
var LocationContext_1 = require("./LocationContext"); | ||
var memoizedQueryParser_1 = require("./memoizedQueryParser"); | ||
var shallowEqual_1 = require("./shallowEqual"); | ||
/** | ||
@@ -32,52 +15,46 @@ * Given a query parameter configuration (mapping query param name to { encode, decode }), | ||
exports.useQueryParams = function (paramConfigMap) { | ||
var _a = React.useContext(QueryParamProvider_1.QueryParamContext), history = _a.history, location = _a.location; | ||
var locationIsObject = typeof location === 'object'; | ||
var _a = LocationContext_1.useLocationContext(), location = _a[0], setLocation = _a[1]; | ||
var decodedValueCacheRef = React.useRef({}); | ||
// memoize paramConfigMap to make the API nicer for consumers. | ||
// otherwise we'd have to useQueryParams(useMemo(() => { foo: NumberParam }, [])) | ||
var prevParamConfigMap = usePrevious(paramConfigMap); | ||
var hasNewParamConfig = isShallowEqual(prevParamConfigMap, paramConfigMap); | ||
// prettier-ignore | ||
var memoParamConfigMap = React.useMemo(function () { return paramConfigMap; }, [ | ||
hasNewParamConfig, | ||
]); | ||
paramConfigMap = memoParamConfigMap; | ||
// ref with current version history object (see #46) | ||
var refHistory = React.useRef(history); | ||
React.useEffect(function () { | ||
refHistory.current = history; | ||
}, [history]); | ||
// ref with current version location object (see #46) | ||
var refLocation = React.useRef(location); | ||
React.useEffect(function () { | ||
refLocation.current = location; | ||
}, [location]); | ||
var search = locationIsObject ? location.search : ''; | ||
paramConfigMap = helpers_1.usePreviousIfShallowEqual(paramConfigMap); | ||
// create a setter for updating multiple query params at once | ||
var setQuery = React.useCallback(function (changes, updateType) { | ||
// encode as strings for the URL | ||
var encodedChanges = serialize_query_params_1.encodeQueryParams(paramConfigMap, changes); | ||
// update the URL | ||
setLocation(encodedChanges, updateType); | ||
}, [paramConfigMap, setLocation]); | ||
// read in the raw query | ||
var rawQuery = React.useMemo(function () { return serialize_query_params_1.parse(search) || {}; }, [ | ||
search, | ||
]); | ||
// parse each parameter via useQueryParam | ||
// we reuse the logic to not recreate objects | ||
var paramNames = Object.keys(paramConfigMap); | ||
var paramValues = paramNames.map(function (paramName) { | ||
return useQueryParam_1.useQueryParam(paramName, paramConfigMap[paramName], rawQuery)[0]; | ||
}); | ||
// we use a memo here to prevent recreating the containing decodedValues object | ||
// which would break === comparisons even if no values changed. | ||
var parsedQuery = memoizedQueryParser_1.sharedMemoizedQueryParser(helpers_1.getSSRSafeSearchString(location)); | ||
// decode all the values if we have changes | ||
var decodedValues = React.useMemo(function () { | ||
// iterate over the decoded values and build an object | ||
var decodedValueCache = decodedValueCacheRef.current; | ||
// we are decoding since the parsed query changed or the param config map has changed. | ||
// recompute new values but only for those that changed | ||
var paramNames = Object.keys(paramConfigMap); | ||
var decodedValues = {}; | ||
for (var i = 0; i < paramNames.length; ++i) { | ||
decodedValues[paramNames[i]] = paramValues[i]; | ||
for (var _i = 0, paramNames_1 = paramNames; _i < paramNames_1.length; _i++) { | ||
var paramName = paramNames_1[_i]; | ||
var paramConfig = paramConfigMap[paramName]; | ||
var encodedValue = parsedQuery[paramName]; | ||
var decodedValue = void 0; | ||
// re-use cached decoded value if we can, otherwise generate new | ||
var cacheEntry = decodedValueCache[paramName]; | ||
if (cacheEntry != undefined && | ||
shallowEqual_1.default(cacheEntry.encodedValue, encodedValue)) { | ||
decodedValue = cacheEntry.decodedValue; | ||
} | ||
else { | ||
if (cacheEntry == null) { | ||
decodedValueCache[paramName] = {}; | ||
} | ||
decodedValue = paramConfig.decode(encodedValue); | ||
decodedValueCache[paramName].encodedValue = encodedValue; | ||
decodedValueCache[paramName].decodedValue = decodedValue; | ||
} | ||
decodedValues[paramName] = decodedValue; | ||
} | ||
return decodedValues; | ||
}, paramValues); // eslint-disable-line react-hooks/exhaustive-deps | ||
// create a setter for updating multiple query params at once | ||
var setQuery = React.useCallback(function (changes, updateType) { | ||
// encode as strings for the URL | ||
var encodedChanges = serialize_query_params_1.encodeQueryParams(paramConfigMap, changes); | ||
// update the URL | ||
updateUrlQuery_1.default(encodedChanges, refHistory.current.location || refLocation.current, // see #46 | ||
refHistory.current, updateType); | ||
}, [paramConfigMap]); | ||
}, [paramConfigMap, parsedQuery]); | ||
// no longer Partial | ||
@@ -84,0 +61,0 @@ return [decodedValues, setQuery]; |
import * as React from 'react'; | ||
import { QueryParamConfigMap, DecodedValueMap } from 'serialize-query-params'; | ||
import { SetQuery } from './types'; | ||
declare type Omit<T, K> = Pick<T, Exclude<keyof T, K>>; | ||
declare type Diff<T, K> = Omit<T, keyof K>; | ||
export interface InjectedQueryProps<QPCMap extends QueryParamConfigMap> { | ||
@@ -15,3 +17,12 @@ query: DecodedValueMap<QPCMap>; | ||
*/ | ||
export declare function withQueryParams<QPCMap extends QueryParamConfigMap, P extends InjectedQueryProps<QPCMap>>(paramConfigMap: QPCMap, WrappedComponent: React.ComponentType<P>): React.FunctionComponent<Pick<P, Exclude<keyof P, "query" | "setQuery">>>; | ||
export declare function withQueryParams<QPCMap extends QueryParamConfigMap, P extends InjectedQueryProps<QPCMap>>(paramConfigMap: QPCMap, WrappedComponent: React.ComponentType<P>): React.FC<Pick<P, Exclude<keyof P, "query" | "setQuery">>>; | ||
export default withQueryParams; | ||
/** | ||
* HOC to provide query parameters via props mapToProps (similar to | ||
* react-redux connect style mapStateToProps) | ||
* NOTE: I couldn't get type to automatically infer generic when | ||
* using the format withQueryParams(config)(component), so I switched | ||
* to withQueryParams(config, component). | ||
* See: https://github.com/microsoft/TypeScript/issues/30134 | ||
*/ | ||
export declare function withQueryParamsMapped<QPCMap extends QueryParamConfigMap, MappedProps extends object, P extends MappedProps>(paramConfigMap: QPCMap, mapToProps: (query: DecodedValueMap<QPCMap>, setQuery: SetQuery<QPCMap>, props: Diff<P, MappedProps>) => MappedProps, WrappedComponent: React.ComponentType<P>): React.FC<Pick<P, Exclude<keyof P, keyof MappedProps>>>; |
@@ -30,5 +30,3 @@ "use strict"; | ||
}; | ||
Component.displayName = "withQueryParams(" + (WrappedComponent.displayName || | ||
WrappedComponent.name || | ||
'Component') + ")"; | ||
Component.displayName = "withQueryParams(" + (WrappedComponent.displayName || WrappedComponent.name || 'Component') + ")"; | ||
return Component; | ||
@@ -38,1 +36,21 @@ } | ||
exports.default = withQueryParams; | ||
/** | ||
* HOC to provide query parameters via props mapToProps (similar to | ||
* react-redux connect style mapStateToProps) | ||
* NOTE: I couldn't get type to automatically infer generic when | ||
* using the format withQueryParams(config)(component), so I switched | ||
* to withQueryParams(config, component). | ||
* See: https://github.com/microsoft/TypeScript/issues/30134 | ||
*/ | ||
function withQueryParamsMapped(paramConfigMap, mapToProps, WrappedComponent) { | ||
// return a FC that takes props excluding query and setQuery | ||
var Component = function (props) { | ||
var _a = useQueryParams_1.default(paramConfigMap), query = _a[0], setQuery = _a[1]; | ||
var propsToAdd = mapToProps(query, setQuery, props); | ||
// see https://github.com/microsoft/TypeScript/issues/28938#issuecomment-450636046 for why `...props as P` | ||
return React.createElement(WrappedComponent, __assign({}, propsToAdd, props)); | ||
}; | ||
Component.displayName = "withQueryParams(" + (WrappedComponent.displayName || WrappedComponent.name || 'Component') + ")"; | ||
return Component; | ||
} | ||
exports.withQueryParamsMapped = withQueryParamsMapped; |
{ | ||
"name": "use-query-params", | ||
"version": "0.6.0", | ||
"version": "1.0.0", | ||
"description": "React Hook for managing state in URL query parameters with easy serialization.", | ||
@@ -44,14 +44,15 @@ "main": "lib/index.js", | ||
"devDependencies": { | ||
"@babel/core": "^7.3.4", | ||
"@babel/preset-env": "^7.3.4", | ||
"@babel/preset-react": "^7.0.0", | ||
"@babel/preset-typescript": "^7.3.3", | ||
"@babel/core": "^7.9.0", | ||
"@babel/preset-env": "^7.9.5", | ||
"@babel/preset-react": "^7.9.4", | ||
"@babel/preset-typescript": "^7.9.0", | ||
"@testing-library/jest-dom": "^4.1.0", | ||
"@testing-library/react": "^9.1.4", | ||
"@testing-library/react-hooks": "^3.2.1", | ||
"@types/jest": "^24.0.11", | ||
"@types/react": "^16.8.25", | ||
"@typescript-eslint/eslint-plugin": "^2.2.0", | ||
"@typescript-eslint/eslint-plugin": "^2.28.0", | ||
"@typescript-eslint/parser": "^2.2.0", | ||
"babel-jest": "^24.5.0", | ||
"eslint": "^6.3.0", | ||
"babel-jest": "^25.3.0", | ||
"eslint": "^6.8.0", | ||
"eslint-plugin-import": "^2.18.2", | ||
@@ -61,10 +62,11 @@ "eslint-plugin-react": "^7.14.3", | ||
"husky": "^3.0.5", | ||
"jest": "^24.5.0", | ||
"jest": "^25.3.0", | ||
"lint-staged": "^9.2.5", | ||
"prettier": "^1.18.2", | ||
"prettier": "^2.0.4", | ||
"query-string": "^6.12.1", | ||
"react": "^16.9.0", | ||
"react-dom": "^16.9.0", | ||
"react-hooks-testing-library": "^0.3.7", | ||
"rimraf": "^3.0.0", | ||
"typescript": "^3.3.3333" | ||
"react-test-renderer": "^16.9.0", | ||
"rimraf": "^3.0.2", | ||
"typescript": "^3.8.3" | ||
}, | ||
@@ -76,3 +78,3 @@ "peerDependencies": { | ||
"dependencies": { | ||
"serialize-query-params": "^0.3.0" | ||
"serialize-query-params": "^1.0.1" | ||
}, | ||
@@ -79,0 +81,0 @@ "husky": { |
@@ -32,5 +32,7 @@ <div align="center"> | ||
``` | ||
$ npm install --save use-query-params | ||
$ npm install --save use-query-params query-string | ||
``` | ||
Note: There is a peer dependency on [query-string](https://github.com/sindresorhus/query-string). For IE11 support, use v5.1.1, otherwise use v6. | ||
Link your routing system (e.g., [React Router example](https://github.com/pbeshai/use-query-params/blob/master/examples/react-router/src/index.tsx), [Reach Router example](https://github.com/pbeshai/use-query-params/blob/master/examples/reach-router/src/index.tsx)): | ||
@@ -58,2 +60,4 @@ | ||
Be sure to add **QueryParamProvider** as shown in Installation above. | ||
Add the hook to your component. There are two options: `useQueryParam`: | ||
@@ -92,2 +96,3 @@ | ||
ArrayParam, | ||
withDefault, | ||
} from 'use-query-params'; | ||
@@ -100,5 +105,5 @@ | ||
q: StringParam, | ||
filters: ArrayParam, | ||
filters: withDefault(ArrayParam, []), | ||
}); | ||
const { x: num, q: searchQuery, filters = [] } = query; | ||
const { x: num, q: searchQuery, filters } = query; | ||
@@ -137,6 +142,7 @@ return ( | ||
ArrayParam, | ||
withDefault, | ||
} from 'use-query-params'; | ||
const WithQueryParamsExample = ({ query, setQuery }: any) => { | ||
const { x: num, q: searchQuery, filters = [] } = query; | ||
const { x: num, q: searchQuery, filters } = query; | ||
@@ -166,3 +172,3 @@ return ( | ||
q: StringParam, | ||
filters: ArrayParam, | ||
filters: withDefault(ArrayParam, []), | ||
}, WithQueryParamsExample); | ||
@@ -180,2 +186,3 @@ ``` | ||
ArrayParam, | ||
withDefault, | ||
} from 'use-query-params'; | ||
@@ -187,3 +194,3 @@ | ||
q: StringParam, | ||
filters: ArrayParam, | ||
filters: withDefault(ArrayParam, []), | ||
}; | ||
@@ -194,3 +201,3 @@ return ( | ||
{({ query, setQuery }) => { | ||
const { x: num, q: searchQuery, filters = [] } = query; | ||
const { x: num, q: searchQuery, filters } = query; | ||
return ( | ||
@@ -250,3 +257,3 @@ <> | ||
For convenience, use-query-params exports all of the [serialize-query-params](https://github.com/pbeshai/serialize-query-params) library. This includes most functions from [query-string](https://github.com/sindresorhus/query-string), which is used internally. | ||
For convenience, use-query-params exports all of the [serialize-query-params](https://github.com/pbeshai/serialize-query-params) library. | ||
@@ -257,13 +264,18 @@ #### UrlUpdateType | ||
- `'pushIn'`: Push just a single parameter, leaving the rest as is (back button works) (the default) | ||
- `'push'`: Push all parameters with just those specified (back button works) | ||
- `'replaceIn'`: Replace just a single parameter, leaving the rest as is | ||
- `'replace'`: Replace all parameters with just those specified | ||
- `'pushIn'`: Push just a single parameter, leaving the rest as is (back button works) | ||
- `'push'`: Push all parameters with just those specified (back button works) | ||
#### Param Types | ||
See [all param definitions from serialize-query-params here](https://github.com/pbeshai/serialize-query-params/blob/master/src/params.ts). You can define your own parameter types by creating an object with an `encode` and a `decode` function. See the existing definitions for examples. | ||
Note that all nully values will encode and decode as `undefined`. | ||
Note that all null and empty values are typically treated as follows: | ||
| value | encoding | | ||
| --- | --- | | ||
| `null` | `?qp` | | ||
| `""` | `?qp=` | | ||
| `undefined` | `?` (removed from URL) | | ||
Examples in this table assume query parameter named `qp`. | ||
@@ -333,3 +345,3 @@ | ||
You may optionally pass in a rawQuery object, otherwise the query is derived | ||
from the location available in the QueryParamContext. | ||
from the location in the context. | ||
@@ -345,2 +357,5 @@ **Example** | ||
setFoo(123, 'push'); | ||
// to unset or remove a parameter set it to undefined and use pushIn or replaceIn update types | ||
setFoo(undefined) // ?foo=123&bar=zzz becomes ?bar=zzz | ||
``` | ||
@@ -373,2 +388,5 @@ | ||
setQuery({ foo: 123, bar: 'zzz' }, 'push'); | ||
// to unset or remove a parameter set it to undefined and use pushIn or replaceIn update types | ||
setQuery({ foo: undefined }) // ?foo=123&bar=zzz becomes ?bar=zzz | ||
``` | ||
@@ -432,2 +450,4 @@ | ||
Note there is also a variant called `withQueryParamsMapped` that allows you to do a react-redux style mapStateToProps equivalent. See [the code](https://github.com/pbeshai/use-query-params/blob/master/src/withQueryParams.tsx#L51) or [this example](https://github.com/pbeshai/use-query-params/blob/master/examples/react-router/src/ReadmeExample3Mapped.tsx) for details. | ||
<br/> | ||
@@ -434,0 +454,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
76865
55
1316
1
546
26
2
1
+ Addeddecode-uri-component@0.4.1(transitive)
+ Addedfilter-obj@5.1.0(transitive)
+ Addedquery-string@9.0.0(transitive)
+ Addedserialize-query-params@1.3.6(transitive)
+ Addedsplit-on-first@3.0.0(transitive)
- Removeddecode-uri-component@0.2.2(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedquery-string@5.1.1(transitive)
- Removedserialize-query-params@0.3.0(transitive)
- Removedstrict-uri-encode@1.1.0(transitive)