You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 20:05:11