基于useSearchParams的状态持久化Hook:多参数同时设置失效问题
问题:React-Router自定义useSearchParamsState Hook同时设置多个参数时仅最后一个生效
我基于React-Router的useSearchParams实现了一个自定义Hook,用来把状态存储在URL查询字符串中,代码如下:
import { useSearchParams } from 'react-router-dom'; const decodeSearchParam = (searchParam: string) => { try { return JSON.parse(searchParam); } catch { return searchParam; } }; function useSearchParamsState<T = string>( searchParamName: string, defaultValue: T, ): readonly [ searchParamsState: T, setSearchParamsState: (newState: T) => void, ] { const [searchParams, setSearchParams] = useSearchParams(); const acquiredSearchParam = searchParams.get(searchParamName); const searchParamsState = acquiredSearchParam ? decodeSearchParam(acquiredSearchParam) : defaultValue; const setSearchParamsState = (newState: T) => { const next = { ...Array.from(searchParams.entries()).reduce( (o, [key, value]) => ({ ...o, [key]: value }), {}, ), [searchParamName]: JSON.stringify(newState), }; setSearchParams(next); }; return [searchParamsState, setSearchParamsState]; } export default useSearchParamsState;
现在遇到一个问题:当同时调用多个这个Hook的set方法时,只有最后一个参数会生效。比如调用设置param2和param3的方法后,最终URL查询字符串里只有param3,param2没有被添加进去。
解决方案
问题出在每次调用setSearchParamsState时,都基于组件渲染时的searchParams快照来构建新参数对象。当你连续调用多个set方法时,每个方法都用的是同一个旧的searchParams状态,后面的调用会覆盖前面的修改,因为它们没有基于最新的URL参数状态进行更新。
要解决这个问题,需要使用setSearchParams的函数式更新方式,让每次更新都基于最新的查询参数状态来构建新值。修改setSearchParamsState的实现:
const setSearchParamsState = (newState: T) => { // 使用函数式更新,prev是最新的SearchParams实例 setSearchParams(prev => { // 把prev转换成普通对象,保留已有参数 const currentParams = Array.from(prev.entries()).reduce( (o, [key, value]) => ({ ...o, [key]: value }), {} ); // 合并新参数并返回 return { ...currentParams, [searchParamName]: JSON.stringify(newState), }; }); };
修改后的完整代码:
import { useSearchParams } from 'react-router-dom'; const decodeSearchParam = (searchParam: string) => { try { return JSON.parse(searchParam); } catch { return searchParam; } }; function useSearchParamsState<T = string>( searchParamName: string, defaultValue: T, ): readonly [ searchParamsState: T, setSearchParamsState: (newState: T) => void, ] { const [searchParams, setSearchParams] = useSearchParams(); const acquiredSearchParam = searchParams.get(searchParamName); const searchParamsState = acquiredSearchParam ? decodeSearchParam(acquiredSearchParam) : defaultValue; const setSearchParamsState = (newState: T) => { setSearchParams(prev => { const currentParams = Array.from(prev.entries()).reduce( (o, [key, value]) => ({ ...o, [key]: value }), {} ); return { ...currentParams, [searchParamName]: JSON.stringify(newState), }; }); }; return [searchParamsState, setSearchParamsState]; } export default useSearchParamsState;
原理说明:
- 函数式更新时,React-Router会传入当前最新的
SearchParams实例作为参数prev,确保每次更新都基于最新的状态。 - 这样连续调用多个
setSearchParamsState时,每个更新都会叠加在前一个的结果上,不会出现覆盖丢失参数的情况。
内容的提问来源于stack exchange,提问作者Croviajo
相关产品推荐
相关产品推荐

