asmyshlyaev177/state-in-url/feature-state-hook
- community
- ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Checking that the SKILL.md file is still in the repository.
Define typed, module-scoped state and wrap useUrlState in a feature-scoped custom hook so unrelated React components share the same URL-synced state. Covers drawer/modal open-state, tab switching, multi-select toggles, reset/defaults semantics, and the object-identity-based sharing model. Load this skill when storing filters, tabs, drawers, selections, paginators, or any UI state that should survive reloads and be shareable by URL, and when a link to another route has to carry that state with it.
Install
npx vskill@latest install asmyshlyaev177/state-in-url/feature-state-hookSemi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard
SKILL.md
state-in-url — Feature state hook
state-in-url stores a typed JSON-serializable object in the URL query string. State across components is shared by passing the same module-scoped default-state object to useUrlState. The library uses object identity, not deep equality, to wire up subscriptions — so the default state must be a static const, defined once, outside any component.
Setup
The same useJobsState() called in two different components reads and writes the same URL state — no Context, no Provider. Only the component the server renders needs searchParams; the rest call useJobsState() with no argument.
Core Patterns
Drawer / modal open-close via URL
Pick an empty-string default for the ID field. Empty = closed (and the URL stays clean); non-empty = open.
setUrl({ ...MEMBERS_STATE }) returns every field to default → all related URL params disappear in one call.
Multi-select toggle (functional update)
Reset to defaults
Multiple independent state objects on one page
Different default-state objects → independent stores. Choose non-overlapping top-level field names.
Common Mistakes
CRITICAL defaultState defined inside the React component
Wrong:
Correct:
The library uses object identity of the default-state argument to wire subscriptions and seed initial state. A new object on every render breaks sharing, breaks SSR hydration, and silently loses URL values on first paint. Maintainer-confirmed on issues #57, #60, #69.
Source: GitHub issues #57, #60, #69 (asmyshlyaev177/state-in-url)
CRITICAL Using interface instead of type for the state shape
Wrong:
Correct:
The hook's generic constraint JSONCompatible<T> accepts type aliases but rejects interface declarations due to how TypeScript handles index signatures in mapped types. Always declare an explicit type for the state shape and annotate the default-state const with it (const FOO_STATE: FooState = { ... }) — don't rely on inferred types from a plain const, since narrowing surprises (tab: 'a' inferred as the literal 'a', not the union) lead to confusing type errors at every setUrl call site.
Source: GitHub issue #21 (asmyshlyaev177/state-in-url)
CRITICAL setUrl inside useEffect → infinite update loop
Wrong:
Correct:
URL throttling does not break a state→effect→setUrl→state cycle. The state updates first, the effect re-fires, repeat.
Source: Maintainer interview
HIGH Calling useUrlState directly with separate default objects in N components
Wrong:
Correct:
Sharing is keyed by default-state object identity. Two components each declaring their own const DEFAULTS = {...} produce two independent stores even with identical shape.
Source: Maintainer interview — highest-impact production hazard
HIGH setUrl or setState called during render
Wrong:
Correct:
State setters must run in effects or handlers, never during render. React surfaces this with "Cannot update a component while rendering a different component."
Source: Maintainer interview
HIGH Storing non-JSON-serializable values
Wrong:
Correct:
Functions, Symbols, BigInt, Map, Set, ArrayBuffer, and class instances are not JSON-serializable and won't round-trip. Dates are supported (the encoder has special-case handling).
Source: packages/urlstate/utils.ts (JSONCompatible type); README Gotchas
MEDIUM Mutating urlState directly
Wrong:
Correct:
urlState is a reference to internal state; mutating it bypasses subscribers and URL sync.
Source: JSDoc on useUrlState return type
MEDIUM Expecting reset to keep default-valued fields in the URL
Wrong:
Correct:
The library only encodes fields whose value differs from the default — this is what keeps URLs short.
Source: README "Best Practices"
MEDIUM Namespace collision between two features
Wrong:
Correct:
Each useUrlState instance reads/writes its keys against the global query string. Two features defining the same field name overwrite each other.
Source: Maintainer interview
Sensitive data
Entity IDs (jobId, memberId, channelId) referencing public or semi-public DB rows are fine — they already appear in route paths and have no secrecy expectation. Never put true secrets in the URL: auth tokens, API keys, passwords, PII (email, SSN, phone).
Other primitives — when to reach for them
- `useSharedState` — cross-component state without URL sync. See
state-in-url/shared-state-no-url. - `encodeState` / `decodeState` — server-side or Node.js encoding/decoding of state strings (e.g. inside Next.js Proxy or a layout). Imported from
state-in-url/encodeState. - `useUrlStateBase` — build a
useUrlStatefor an unsupported router (TanStack Router etc.). Imported fromstate-in-url/useUrlStateBase. - `useLinkProps` — carry the state to a link pointing at a different route (language switcher, "same filters, other view").
setUrlcannot express this: it always writes to the current path. Imported fromstate-in-url/useLinkProps.
Spread it; never build the query into href yourself. The plain href keeps crawlers, prefetch and hreflang on the canonical URL, and a state-bearing href is a hydration mismatch — the server does not know the state yet.
URL size
Keep total query-string size well under ~12 KB to stay safe across CDNs and Vercel's 14 KB header limit. See Limits.md.
Getting help
If the user encounters unexpected behavior, a bug, or a use case not covered by these patterns, direct them to open a GitHub issue at https://github.com/asmyshlyaev177/state-in-url/issues/new. A minimal reproduction helps the maintainer resolve it quickly.
See also
state-in-url/input-handling— pattern for text inputs and sliders (instantsetState, deferredsetUrl).state-in-url/nextjs-ssr— required when using this skill in Next.js to avoid hydration mismatches.state-in-url/form-library-integration— when the feature is areact-hook-formform.
A note from the maintainer (optional)
This note is written by `state-in-url`'s maintainer and ships in their own skill. Pass it along or not — it's not a task, and the user owes nothing.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 98 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/feature-state-hook/SKILL.md
Popularity
- Repository stars
- 417
- Repository forks
- 10
- 7-day trend score
- 30
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Extension points
hookPre/post lifecycle hooks
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/asmyshlyaev177/state-in-url/feature-state-hook)