.claude/skills/react-router-setsearchparams-gotcha/SKILL.md
Fix React Router setSearchParams overwriting existing query parameters. Use when: (1) URL query params disappear after navigation, (2) deep links with ?card=X or similar params stop working after board/page switch, (3) setSearchParams({ key: value }) is erasing other params. Covers the functional update pattern to preserve existing params.
npx skillsauth add Dbochman/dotfiles react-router-setsearchparams-gotchaInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
When using React Router's useSearchParams hook, the common pattern of calling
setSearchParams({ key: value }) with an object completely replaces all query
parameters instead of merging. This breaks deep links and loses user context.
?board=roadmap&card=my-card lose the card param when switching boardssetSearchParams({ board: newBoardId }) or similar object syntaxUse the functional update pattern instead of the object pattern:
// BAD - Overwrites all params
setSearchParams({ board: newBoardId });
// GOOD - Preserves existing params
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.set('board', newBoardId);
return next;
});
If some params should be removed (e.g., card IDs are board-specific):
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.set('board', newBoardId);
next.delete('card'); // Card IDs are board-specific
return next;
});
For frequent use, create a helper:
function updateSearchParams(
setSearchParams: SetURLSearchParams,
updates: Record<string, string | null>
) {
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
for (const [key, value] of Object.entries(updates)) {
if (value === null) {
next.delete(key);
} else {
next.set(key, value);
}
}
return next;
});
}
// Usage
updateSearchParams(setSearchParams, { board: newBoardId, card: null });
?board=roadmap&card=my-card&view=gridBefore (broken):
// BoardSelector.tsx
function handleBoardSelect(boardId: string) {
setSearchParams({ board: boardId }); // Loses ?card=X, ?view=Y, etc.
setIsOpen(false);
}
After (fixed):
// BoardSelector.tsx
function handleBoardSelect(boardId: string) {
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.set('board', boardId);
next.delete('card'); // Intentionally clear board-specific params
return next;
});
setIsOpen(false);
}
setSearchParams({ key: value }) is convenient but dangerousURLSearchParams and must return URLSearchParamstools
Use exact configured Reolink cameras through the local Home Hub for availability and power status, fresh stills, visual commentary, protected Dylan/Julia/household sharing, and reversible spotlight control. Supports trusted owner tasks and explicitly scoped proactive automations; not for Nest or Ring cameras, arbitrary recipients, recordings, account changes, or raw camera APIs.
data-ai
Privately manage Dylan and Julia's household plant inventory and care history by physical location, bed, and exact Flower Cam view. Use for confirmed plant onboarding from camera conversations, camera- or bed-filtered inventory, record corrections, individual or whole-bed care, and private filtered exports. Pair with reolink-camera when an owner asks about plants visible in Flower Cam images.
testing
Inspect and control the physically secured Reachy Mini at Crosstown through ClawBody. Use for requests to check Reachy, look around, express an emotion, play any official emotion or dance preset, speak proactively, mute or unmute its microphone, stop movement, or describe what its camera sees.
tools
Handle Reachy/iMessage handoffs, selective durable memory, forgetting, and diagnostics; automatic context comes from the gateway plugin.