search-params

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

URL Search Param State Management

URL搜索参数状态管理

Decision Framework

决策框架

When updating the URL (search params or hash), choose between replace and push based on whether the change represents in-page state or a user-navigable step:
Change typeExamplesHistory behavior
In-page stateFilters, sort, pagination, tab switches, search queries
replace
- don't pollute history
Navigable stepWizard progression, multi-step forms
push
- back button should return to previous step
Unsure?Ask the developer before choosing
Why this matters: Pushing in-page state changes clutters the browser history. Users clicking "back" expect to leave the page, not undo a filter toggle. This is the #1 cause of "back button is broken" bugs.
更新URL(搜索参数或哈希)时,需根据变更代表的是页面内状态还是用户可导航步骤,在replacepush之间做出选择:
变更类型示例历史记录行为
页面内状态筛选器、排序、分页、标签切换、搜索查询
replace
- 不污染历史记录
可导航步骤向导流程推进、多步骤表单
push
- 后退按钮应返回上一步
不确定?选择前咨询开发人员
重要性说明: 将页面内状态变更加入历史记录会使浏览器历史记录杂乱。用户点击“后退”按钮时期望离开当前页面,而非撤销筛选器切换操作。这是“后退按钮失效”类Bug的头号诱因。

Correct Patterns

正确实践

Single search param - use
useSearchParamState
(preferred)

单个搜索参数 - 使用
useSearchParamState
(推荐)

This hook validates with Zod and always uses
replace: true
internally
, so you get correct history behavior for free.
tsx
import { useSearchParamState } from '@app/hooks/useSearchParamState';
import { z } from 'zod';

const TabSchema = z.enum(['overview', 'details', 'settings']);
const [activeTab, setActiveTab] = useSearchParamState('tab', TabSchema, 'overview');
Key file:
src/app/src/hooks/useSearchParamState.ts
该钩子通过Zod进行验证,并且内部始终使用
replace: true
,因此你可以自动获得正确的历史记录行为。
tsx
import { useSearchParamState } from '@app/hooks/useSearchParamState';
import { z } from 'zod';

const TabSchema = z.enum(['overview', 'details', 'settings']);
const [activeTab, setActiveTab] = useSearchParamState('tab', TabSchema, 'overview');
关键文件:
src/app/src/hooks/useSearchParamState.ts

Multiple search params - use
setSearchParams
with
replace: true

多个搜索参数 - 使用
setSearchParams
并传入
replace: true

When updating multiple params at once, use
setSearchParams
directly but always pass
{ replace: true }
for in-page state:
tsx
const [searchParams, setSearchParams] = useSearchParams();

// Updating filters (in-page state -> replace)
setSearchParams(
  (params) => {
    params.set('status', 'active');
    params.set('sort', 'name');
    return params;
  },
  { replace: true },
);
同时更新多个参数时,直接使用
setSearchParams
,但对于页面内状态变更,务必传入
{ replace: true }
tsx
const [searchParams, setSearchParams] = useSearchParams();

// 更新筛选器(页面内状态 -> 使用replace)
setSearchParams(
  (params) => {
    params.set('status', 'active');
    params.set('sort', 'name');
    return params;
  },
  { replace: true },
);

Hash-based navigation -
navigate()
with replace or push

基于哈希的导航 - 结合replace或push使用
navigate()

For wizard/multi-step flows where back button should traverse steps, use push (the default):
tsx
// Wizard step navigation - push so back button works between steps
// See: src/app/src/pages/redteam/setup/page.tsx
const updateHash = (newStep: string) => {
  navigate(`#${newStep}`); // push (default) - intentional
};
For hash changes that represent in-page state, use replace:
tsx
// Tab switch on a detail page - replace to avoid history clutter
navigate(`#${section}`, { replace: true });
对于后退按钮应能遍历步骤的向导/多步骤流程,使用push(默认行为):
tsx
// 向导步骤导航 - 使用push使后退按钮可在步骤间切换
// 参考:src/app/src/pages/redteam/setup/page.tsx
const updateHash = (newStep: string) => {
  navigate(`#${newStep}`); // push(默认)- 有意为之
};
对于代表页面内状态的哈希变更,使用replace:
tsx
// 详情页标签切换 - 使用replace避免历史记录杂乱
navigate(`#${section}`, { replace: true });

URL normalization after save

保存后的URL标准化

When the URL needs to be updated to include a new ID after a create/save operation (not a user action), use replace:
tsx
// After first save, update URL to include new ID without adding history entry
navigate(`/evals/${newConfigId}`, { replace: true });
当创建/保存操作后需要更新URL以包含新ID(非用户主动操作)时,使用replace:
tsx
// 首次保存后,更新URL以包含新ID,不添加历史记录条目
navigate(`/evals/${newConfigId}`, { replace: true });

Anti-Patterns

反模式

Pushing in-page state changes (breaks back button)

将页面内状态变更加入历史记录(破坏后退按钮)

tsx
// WRONG - every filter change adds a history entry
setSearchParams((params) => {
  params.set('filter', value);
  return params;
});

// WRONG - navigate without replace for state change
navigate(`?tab=${newTab}`);
tsx
// 错误 - 每次筛选器变更都会添加一条历史记录
setSearchParams((params) => {
  params.set('filter', value);
  return params;
});

// 错误 - 状态变更时调用navigate未传入replace
navigate(`?tab=${newTab}`);

Using raw
useSearchParams
for a single param without validation

未验证的情况下对单个参数使用原生
useSearchParams

tsx
// WRONG - no validation, easy to forget { replace: true }
const [searchParams, setSearchParams] = useSearchParams();
const tab = searchParams.get('tab');
const setTab = (v: string) => {
  setSearchParams((p) => {
    p.set('tab', v);
    return p;
  });
};

// RIGHT - use the hook instead
const [tab, setTab] = useSearchParamState('tab', TabSchema, 'overview');
tsx
// 错误 - 无验证,容易忘记传入{ replace: true }
const [searchParams, setSearchParams] = useSearchParams();
const tab = searchParams.get('tab');
const setTab = (v: string) => {
  setSearchParams((p) => {
    p.set('tab', v);
    return p;
  });
};

// 正确 - 使用钩子替代
const [tab, setTab] = useSearchParamState('tab', TabSchema, 'overview');

Using empty strings instead of null

使用空字符串而非null

tsx
// WRONG - useSearchParamState will throw an invariant error
setTab('');

// RIGHT - use null to clear a param
setTab(null);
tsx
// 错误 - useSearchParamState会抛出不变量错误
setTab('');

// 正确 - 使用null清除参数
setTab(null);

Key Files

关键文件

  • src/app/src/hooks/useSearchParamState.ts
    - primary hook (uses replace internally)
  • src/app/src/pages/eval/components/ResultsView.tsx
    - example of correct
    { replace: true }
    usage
  • src/app/src/pages/redteam/setup/page.tsx
    - example of intentional push for wizard steps
  • src/app/src/hooks/useSearchParamState.ts
    - 核心钩子(内部使用replace)
  • src/app/src/pages/eval/components/ResultsView.tsx
    - 正确使用
    { replace: true }
    的示例
  • src/app/src/pages/redteam/setup/page.tsx
    - 向导流程中有意使用push的示例