Last active
March 25, 2024 08:31
-
-
Save librz/67839609a80dfcc81d9767c667c77882 to your computer and use it in GitHub Desktop.
解决 react-router-dom v5 没有 useSearchParams 的问题
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 以下代码来自 react-router-dom v6,用于解决 react-router-dom v5 没有 useSearchParams 的问题 | |
* see: https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/index.tsx#L1432 | |
* 在原代码的基础上做的改动: | |
* 1. 原代码使用了 useNavigate,但 react-router v5 还没有这个 hook,使用 useHistory 代替(https://reactrouter.com/en/main/upgrading/v5#use-usenavigate-instead-of-usehistory) | |
* 2. 在返回的数组中提供 updateSearchParams 以在保留其他参数的情况下设置/更新指定的参数 | |
*/ | |
import React, { useCallback } from 'react'; | |
import { useLocation, useHistory } from 'react-router'; | |
type ParamKeyValuePair = [string, string]; | |
type URLSearchParamsInit = | |
| string | |
| ParamKeyValuePair[] | |
| Record<string, string | string[]> | |
| URLSearchParams; | |
type NavigateOptions = { | |
replace?: boolean; | |
state?: unknown; | |
}; | |
type SetURLSearchParams = ( | |
nextInit?: URLSearchParamsInit | ((prev: URLSearchParams) => URLSearchParamsInit), | |
navigateOptions?: NavigateOptions | |
) => void; | |
type UpdateURLSearchParams = ( | |
params: Record<string, string | null | undefined>, | |
navigateOptions?: NavigateOptions | |
) => void; | |
/** | |
* Creates a URLSearchParams object using the given initializer. | |
* | |
* This is identical to `new URLSearchParams(init)` except it also | |
* supports arrays as values in the object form of the initializer | |
* instead of just strings. This is convenient when you need multiple | |
* values for a given key, but don't want to use an array initializer. | |
* | |
* For example, instead of: | |
* | |
* let searchParams = new URLSearchParams([ | |
* ['sort', 'name'], | |
* ['sort', 'price'] | |
* ]); | |
* | |
* you can do: | |
* | |
* let searchParams = createSearchParams({ | |
* sort: ['name', 'price'] | |
* }); | |
*/ | |
function createSearchParams(init: URLSearchParamsInit = ''): URLSearchParams { | |
return new URLSearchParams( | |
typeof init === 'string' || Array.isArray(init) || init instanceof URLSearchParams | |
? init | |
: Object.keys(init).reduce((memo, key) => { | |
let value = init[key]; | |
return memo.concat(Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]); | |
}, [] as ParamKeyValuePair[]) | |
); | |
} | |
export function getSearchParamsForLocation( | |
locationSearch: string, | |
defaultSearchParams: URLSearchParams | null | |
) { | |
let searchParams = createSearchParams(locationSearch); | |
if (defaultSearchParams) { | |
// Use `defaultSearchParams.forEach(...)` here instead of iterating of | |
// `defaultSearchParams.keys()` to work-around a bug in Firefox related to | |
// web extensions. Relevant Bugzilla tickets: | |
// https://bugzilla.mozilla.org/show_bug.cgi?id=1414602 | |
// https://bugzilla.mozilla.org/show_bug.cgi?id=1023984 | |
defaultSearchParams.forEach((_, key) => { | |
if (!searchParams.has(key)) { | |
defaultSearchParams.getAll(key).forEach((value) => { | |
searchParams.append(key, value); | |
}); | |
} | |
}); | |
} | |
return searchParams; | |
} | |
/** | |
* A convenient wrapper for reading and writing search parameters via the | |
* URLSearchParams interface. | |
*/ | |
export function useSearchParams( | |
defaultInit?: URLSearchParamsInit | |
): [URLSearchParams, SetURLSearchParams, UpdateURLSearchParams] { | |
let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit)); | |
let hasSetSearchParamsRef = React.useRef(false); | |
let location = useLocation(); | |
let searchParams = React.useMemo( | |
() => | |
// Only merge in the defaults if we haven't yet called setSearchParams. | |
// Once we call that we want those to take precedence, otherwise you can't | |
// remove a param with setSearchParams({}) if it has an initial value | |
getSearchParamsForLocation( | |
location.search, | |
hasSetSearchParamsRef.current ? null : defaultSearchParamsRef.current | |
), | |
[location.search] | |
); | |
let history = useHistory(); | |
let setSearchParams = React.useCallback( | |
( | |
nextInit?: URLSearchParamsInit | ((prev: URLSearchParams) => URLSearchParamsInit), | |
navigateOptions?: NavigateOptions | |
) => { | |
const newSearchParams = createSearchParams( | |
typeof nextInit === 'function' ? nextInit(searchParams) : nextInit | |
); | |
hasSetSearchParamsRef.current = true; | |
if (navigateOptions?.replace) { | |
history.replace(`?${newSearchParams}`, navigateOptions.state); | |
} else { | |
history.push(`?${newSearchParams}`, navigateOptions?.state); | |
} | |
}, | |
[history, searchParams] | |
); | |
// perserve other keys when seting search params | |
const updateSearchParams = useCallback( | |
(params: Record<string, string | null | undefined>, navigateOptions?: NavigateOptions) => { | |
setSearchParams((prev) => { | |
const newSearchParams = new URLSearchParams(prev); | |
Object.entries(params).forEach(([key, value]) => { | |
if (!value) { | |
newSearchParams.delete(key); | |
} else { | |
newSearchParams.set(key, value); | |
} | |
}); | |
return newSearchParams; | |
}, navigateOptions); | |
}, | |
[setSearchParams] | |
); | |
return [searchParams, setSearchParams, updateSearchParams]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useMemo } from 'react'; | |
import { useSearchParams } from './useSearchParams'; | |
import { isNil, omitBy } from 'lodash'; | |
export function useStructQuery<T = any>(queryKey = 'query') { | |
// eslint-disable-next-line | |
const [searchParams, _, updateSearchParams] = useSearchParams(); | |
const queryString = searchParams.get(queryKey); | |
const queryStruct = useMemo(() => { | |
if (!queryString) return null; | |
let result: Partial<{ [key in keyof T]: string }> = {}; | |
try { | |
const parsed = JSON.parse(queryString); | |
result = omitBy(parsed, isNil); | |
} catch (e) { | |
console.error(`JSON.parse failed for query string: ${queryString}`, e); | |
result = {}; | |
} | |
return result; | |
}, [queryString]); | |
const setQueryStruct = useCallback( | |
(newStruct: Partial<T> | null) => { | |
const struct = omitBy(newStruct, isNil); | |
if (!struct) { | |
updateSearchParams({ [queryKey]: null }); | |
return; | |
} | |
if (Object.keys(struct).length === 0) { | |
updateSearchParams({ [queryKey]: null }); | |
return; | |
} | |
// stringify | |
let newQueryString: string | null = null; | |
try { | |
newQueryString = JSON.stringify(struct); | |
} catch (e) { | |
console.error(`JSON.stringify failed for query struct:`, struct, e); | |
newQueryString = null; | |
} | |
updateSearchParams({ [queryKey]: newQueryString }); | |
}, | |
[queryKey, updateSearchParams] | |
); | |
return [queryStruct, setQueryStruct] as const; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment