-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
refactor(router-core): simplify defaultParseSearch & defaultStringifySearch #4985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(router-core): simplify defaultParseSearch & defaultStringifySearch #4985
Conversation
WalkthroughPublic exports encode/decode were removed from React and Solid router barrels. Core query string utilities were refactored: encode/decode signatures changed, array handling and prefix support updated, and URLSearchParams adopted. Search params helpers now delegate encoding via a replacer, avoid input mutation, and simplify error handling. Tests updated accordingly. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller
participant stringifySearchWith
participant encode
participant URLSearchParams
Caller->>stringifySearchWith: (search, parser)
stringifySearchWith->>encode: (search, stringifyValue)
encode->>URLSearchParams: set(key, stringify(value))
URLSearchParams-->>encode: toString()
encode-->>stringifySearchWith: "k=v&arr=a%2Cb"
stringifySearchWith-->>Caller: "?k=v&arr=a%2Cb"
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
View your CI Pipeline Execution ↗ for commit edfec21
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
packages/router-core/src/qss.ts (3)
9-12
: Clarify header comment to reflect API divergence from original qssThe note still implies parity with the original interface, but this fork now intentionally diverges (no prefix, different signatures, single-value-per-key encoding). Please amend to prevent confusion.
- * - * Update: this implementation has also been mangled to - * fit exactly our use-case (single value per key in encoding). + * + * Update: this implementation intentionally diverges from the original: + * - encodes a single value per key (arrays are joined by "," by default) + * - removes the "prefix" parameter + * - updates signatures: encode(obj, stringify?) and decode(str)
31-36
: Avoid inherited properties in encode iterationfor..in will traverse the prototype chain. Use Object.keys (or hasOwnProperty) to ensure only own enumerable props are encoded.
- for (const key in obj) { - const val = obj[key] + for (const key of Object.keys(obj)) { + const val = obj[key] if (val !== undefined) { - result.set(key, stringify(val)) + // Ensure replacer output is a string + const str = stringify(val) as unknown + result.set(key, typeof str === 'string' ? str : String(str)) } }
65-82
: Tighten types and prefer strict equality in decodeMinor polish: use a stricter signature and avoid == null. Also iterate URLSearchParams directly (it’s iterable).
-export function decode(str: any): any { - const searchParams = new URLSearchParams(str) - - const result: Record<string, unknown> = {} - - for (const [key, value] of searchParams.entries()) { - const previousValue = result[key] - if (previousValue == null) { +export function decode(str: string): Record<string, unknown> { + const searchParams = new URLSearchParams(str) + const result: Record<string, unknown> = {} + for (const [key, value] of searchParams) { + const previousValue = result[key] + if (previousValue === undefined) { result[key] = toValue(value) } else if (Array.isArray(previousValue)) { previousValue.push(toValue(value)) } else { result[key] = [previousValue, toValue(value)] } } return result }packages/router-core/tests/qss.test.ts (1)
17-21
: LGTM: array encoding expectation updated to comma-joined single keyMatches the new encode behavior (single value per key; default String on arrays => comma-join).
Consider adding a test to assert undefined values are skipped by encode, e.g.:
it('skips undefined values', () => { expect(encode({ a: 'x', b: undefined })).toEqual('a=x') })packages/router-core/src/searchParams.ts (1)
38-57
: Ensure stringifyValue always returns a stringIf JSON.stringify throws (e.g., circular refs) or when parser path doesn’t trigger, the current fallback returns the original value (object/function/etc.). That relies on implicit coercion later. Make the return value explicitly a string to keep the contract with encode’s replacer.
function stringifyValue(val: any) { if (typeof val === 'object' && val !== null) { try { return stringify(val) } catch { // silent } } else if (hasParser && typeof val === 'string') { try { // Check if it's a valid parseable string. // If it is, then stringify it again. parser(val) return stringify(val) } catch { // silent } } - return val + return String(val) }Note: undefined keys are still skipped by encode because it checks the original value before calling the replacer.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
packages/react-router/src/index.tsx
(0 hunks)packages/router-core/src/index.ts
(0 hunks)packages/router-core/src/qss.ts
(2 hunks)packages/router-core/src/searchParams.ts
(3 hunks)packages/router-core/tests/qss.test.ts
(1 hunks)packages/solid-router/src/index.tsx
(0 hunks)
💤 Files with no reviewable changes (3)
- packages/react-router/src/index.tsx
- packages/solid-router/src/index.tsx
- packages/router-core/src/index.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/router-core/src/searchParams.ts (1)
packages/router-core/src/qss.ts (1)
encode
(25-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Preview
- GitHub Check: Test
🔇 Additional comments (2)
packages/router-core/src/searchParams.ts (2)
12-14
: LGTM: simpler leading-? handlingIndex-check is clear and avoids substring allocation.
59-62
: LGTM: non-mutating stringify via encode(search, replacer)Good shift from mutating input to a replacer-based approach. This aligns with the updated qss encode and keeps behavior centralized.
More templates
@tanstack/arktype-adapter
@tanstack/directive-functions-plugin
@tanstack/eslint-plugin-router
@tanstack/history
@tanstack/react-router
@tanstack/react-router-devtools
@tanstack/react-router-ssr-query
@tanstack/react-start
@tanstack/react-start-client
@tanstack/react-start-plugin
@tanstack/react-start-server
@tanstack/router-cli
@tanstack/router-core
@tanstack/router-devtools
@tanstack/router-devtools-core
@tanstack/router-generator
@tanstack/router-plugin
@tanstack/router-ssr-query-core
@tanstack/router-utils
@tanstack/router-vite-plugin
@tanstack/server-functions-plugin
@tanstack/solid-router
@tanstack/solid-router-devtools
@tanstack/solid-start
@tanstack/solid-start-client
@tanstack/solid-start-plugin
@tanstack/solid-start-server
@tanstack/start-client-core
@tanstack/start-plugin-core
@tanstack/start-server-core
@tanstack/start-server-functions-client
@tanstack/start-server-functions-fetcher
@tanstack/start-server-functions-server
@tanstack/start-storage-context
@tanstack/valibot-adapter
@tanstack/virtual-file-routes
@tanstack/zod-adapter
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM noting #4985 (comment)
…Search (#4985) Wait for this PR before merging: #4987. It adds unit tests that should help make sure we're not breaking anything here. --- The functions used to serialize and deserialize search params are called *a lot*, but they are creating many unnecessary objects. This PR refactors - `encode` to be only as generic as what we need => in practice we only need to handle 1 value per key (i.e. `.set()` and not `.append()`) so we don't need a special case for arrays - `decode` can avoid creating many arrays - `stringifySearchWith` (and `defaultStringifySearch`) don't need to pre-stringify the input into a `<string,string>` dictionary as `encode` now accepts a stringifier function <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - Breaking Changes - Removed encode and decode from React Router and Solid Router public exports. - Query string prefix support removed in encode/decode. - Refactor - Updated query string handling: arrays encode as comma-separated values; duplicate keys decode into arrays. - Streamlined error handling and avoided input mutation; consistent leading “?” management. - Tests - Updated tests to match new encoding/decoding behavior and removed prefix-related cases. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Wait for this PR before merging: #4987. It adds unit tests that should help make sure we're not breaking anything here.
The functions used to serialize and deserialize search params are called a lot, but they are creating many unnecessary objects. This PR refactors
encode
to be only as generic as what we need => in practice we only need to handle 1 value per key (i.e..set()
and not.append()
) so we don't need a special case for arraysdecode
can avoid creating many arraysstringifySearchWith
(anddefaultStringifySearch
) don't need to pre-stringify the input into a<string,string>
dictionary asencode
now accepts a stringifier functionSummary by CodeRabbit