Skip to content

Conversation

@goldflag
Copy link
Collaborator

@goldflag goldflag commented Nov 17, 2025

  • Replaced useSearchParams with useQueryState from the nuqs library across multiple components for better state synchronization with URL parameters.
  • Updated components to utilize new query state management, enhancing clarity and reducing reliance on direct URL manipulation.
  • Improved overall code maintainability and readability by streamlining parameter handling logic.

Summary by CodeRabbit

  • Refactor
    • Improved URL query parameter handling across the application for more reliable state management.
    • Standardized query parameter parsing throughout multiple pages and components.
    • Enhanced URL state synchronization to better preserve user parameters across navigation and page interactions.

- Replaced `useSearchParams` with `useQueryState` from the `nuqs` library across multiple components for better state synchronization with URL parameters.
- Updated components to utilize new query state management, enhancing clarity and reducing reliance on direct URL manipulation.
- Improved overall code maintainability and readability by streamlining parameter handling logic.
@vercel
Copy link

vercel bot commented Nov 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
rybbit Ready Ready Preview Comment Nov 17, 2025 7:24pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

Walkthrough

This PR migrates URL query parameter handling from Next.js's native useSearchParams hook to the nuqs library across multiple pages and components. It introduces centralized parser definitions in a new utility file for consistent parameter parsing, removes Suspense-wrapped components that previously handled URL state, and refactors two core utilities to use nuqs hooks instead of manual parameter retrieval and serialization logic.

Changes

Cohort / File(s) Summary
Page component migrations using useQueryState/useQueryStates
client/src/app/[site]/gsc/select-property/page.tsx, client/src/app/as/callback/page.tsx, client/src/app/invitation/page.tsx, client/src/app/signup/page.tsx
Replaced useSearchParams with useQueryState or useQueryStates from nuqs; removed Suspense-wrapped handler components; synchronized URL params into local state via useEffect where needed.
Utility refactors to nuqs
client/src/app/[site]/utils.ts, client/src/components/SiteSettings/GSCManager.tsx
Replaced useSearchParams with useQueryState from nuqs; updated conditional checks to use parsed boolean/string values directly instead of string comparisons.
Core URL parameter state management
client/src/lib/urlParams.ts
Refactored serialization/deserialization to use nuqs-based URL state flow; replaced manual state sync with nuqs useQueryStates and analyticsParsers; adjusted initialization to read from URL before applying store updates.
Centralized parser definitions
client/src/lib/parsers.ts
Added comprehensive set of custom parsers: basic optional variants, specialized validators for TimeBucket/StatType/TimeMode/wellKnown presets, ISO date and JSON parsers, and composite parser groups for invitations, AppSumo callbacks, and analytics parameters.
Query parameter preservation
client/src/app/[site]/components/Sidebar/SiteSelector.tsx
Replaced useSearchParams().toString() with window.location.search to preserve existing query parameters when navigating.
Cleanup
client/src/app/auth/subscription/success/page.tsx
Removed unused useSearchParams import; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    participant Old as Old Pattern (useSearchParams + Suspense)
    participant New as New Pattern (nuqs hooks)
    
    rect rgb(200, 220, 255)
    note over Old: Initial page load
    Old->>Old: Render parent with Suspense boundary
    Old->>Old: Suspense renders fallback
    Old->>Old: Handler component mounts (async)
    Old->>Old: useSearchParams() reads URL
    Old->>Old: Manual parse + decode
    Old->>Old: setState update
    Old->>Old: Re-render with state
    end
    
    rect rgb(200, 255, 220)
    note over New: Initial page load
    New->>New: useQueryState/useQueryStates hook
    New->>New: nuqs parser auto-converts
    New->>New: State synced via useEffect
    New->>New: Single render cycle
    end
    
    rect rgb(255, 220, 220)
    note over Old,New: URL query parameter changes
    Old->>Old: Re-run handler useEffect
    Old->>Old: Parse & update state
    New->>New: nuqs detects URL change
    New->>New: Parser re-runs automatically
    New->>New: State updates reactively
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–25 minutes

  • Repetitive pattern application: Six page components follow the same useSearchParamsuseQueryState(s) refactor, reducing cognitive load per file despite multiple affected files.
  • Complex refactors requiring careful attention:
    • client/src/lib/urlParams.ts: Major refactor of initialization flow and state synchronization; verify that URL-to-store and store-to-URL flows don't cause unintended overwrites or infinite loops.
    • client/src/lib/parsers.ts: Extensive new parser definitions; verify parser logic matches intended URL parameter semantics and that composite parser groups are correctly scoped.
  • Component removal verification: Confirm that removal of AppSumoCodeHandler and StepHandler Suspense boundaries does not introduce hydration mismatches or timing issues in callback/page.tsx and signup/page.tsx.
  • Hook dependency chains: Verify that all new useQueryState/useQueryStates hooks have correct dependencies and don't trigger unnecessary re-renders or state loops.

Possibly related PRs

  • Update client dependencies and enhance subscription flow #698: Adds the nuqs library dependency and NuqsAdapter setup in layout, providing the foundation that this PR leverages to replace useSearchParams across the codebase.
  • Signup refactor #491: Modifies client/src/app/signup/page.tsx signup step handling and URL parameter logic; may overlap or require conflict resolution with step parameter migration in this PR.

Poem

🐰 Hop, skip, jump through nuqs so neat,
Query params parsed—no more deceit!
Suspense bounds fade, hooks run light,
URL state flows, oh what a sight!
From Next to nuqs, the path is clear,

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the primary change: refactoring URL parameter handling by replacing useSearchParams with nuqs library for improved state management.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nuqs

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining why this PR is needed, why this solution was chosen, and what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 (9)
client/src/app/as/callback/page.tsx (1)

47-55: Consider bidirectional URL sync for improved UX.

The current implementation performs one-way synchronization from URL parameters to local state. When users progress through steps (lines 114, 185), the URL remains unchanged. This means users cannot bookmark their progress, and refreshing the page will reset them to the URL's step.

For better user experience, consider using the setter from useQueryStates to update the URL when currentStep changes:

  // Get code and step from URL params
- const [{ code, step: stepParam }] = useQueryStates({
+ const [{ code, step: stepParam }, setQueryStates] = useQueryStates({
    code: parseAsString,
    step: parseAsInteger,
  });

  // Sync URL params with local state
  useEffect(() => {
    if (code) {
      setAppsumoCode(code);
    }
    if (stepParam && stepParam >= 1 && stepParam <= 3) {
      setCurrentStep(stepParam);
    }
  }, [code, stepParam]);
+
+ // Sync currentStep back to URL
+ useEffect(() => {
+   if (currentStep >= 1 && currentStep <= 3) {
+     setQueryStates({ step: currentStep });
+   }
+ }, [currentStep, setQueryStates]);

This would allow users to refresh at any point without losing progress and enable bookmarking/sharing the current step.

client/src/app/[site]/gsc/select-property/page.tsx (1)

21-25: properties parsing is safe but could validate shape more defensively

The parseAsJson validator currently just casts the decoded value to string[], so malformed JSON (e.g., {} or ["foo", 1]) would flow through unchecked. Since this comes from the URL, consider tightening the validator to enforce an array-of-strings shape (e.g., Array.isArray(value) plus a typeof v === "string" filter) before accepting it.

client/src/components/SiteSettings/GSCManager.tsx (1)

17-29: Consider clearing gsc after handling success to avoid repeated toasts

Reading gscStatus via useQueryState and triggering the success toast/refetch is correct. If you want to avoid re-showing the “connected successfully” toast on every reload/back nav while ?gsc=success is in the URL, you could also grab the setter from useQueryState and set the param back to null once handled.

client/src/app/signup/page.tsx (1)

38-45: Step URL param is now one‑way (URL → state); consider 2‑way sync if needed

Reading step via useQueryState and clamping to 1–3 is solid. Note that currentStep changes (e.g., setCurrentStep(2) after signup) no longer update the step query param, so refreshing or sharing the URL won’t reflect the current step unless it came from the URL initially. If preserving 2‑way sync is important, you could also grab the setter from useQueryState and update step whenever currentStep changes.

client/src/app/invitation/page.tsx (1)

18-21: Query parsing via useQueryStates looks good; consider DRYing/shared defaults

Both AuthComponent and AcceptInvitationInner now independently call useQueryStates to read organization, inviterEmail, and invitationId, which is correct but slightly duplicated. You could optionally:

  • Extract a small useInvitationQuery() hook that returns these values once, or
  • Add parser defaults (e.g., via .withDefault("")) to avoid any accidental "undefined" text if params are missing.

Not required, but would tighten things up a bit.

Also applies to: 52-56

client/src/lib/urlParams.ts (1)

127-181: needsUpdate flag is never used

Within the initialization effect, needsUpdate is toggled but never read, so it no longer affects behavior. It can be safely removed (along with its assignments) to keep the effect focused on the actual URL → store synchronization logic.

client/src/lib/parsers.ts (3)

39-50: Tighten timeMode parser typing to use the Time union

Right now timeModeValues is typed as string[], so parseAsTimeMode will be typed as a generic string parser instead of the Time union you import from @/components/DateSelector/types. To keep things type‑safe and catch drift:

const timeModeValues: Time[] = [
  "day",
  "range",
  "week",
  "month",
  "year",
  "all-time",
  "past-minutes",
];

export const parseAsTimeMode = parseAsStringEnum<Time>(timeModeValues);

This ensures the parser stays in sync with the Time domain type if it changes.


74-80: Align parseAsIsoDate behavior with its comment and consider light validation for JSON parsers

  • parseAsIsoDate is currently just an alias for parseAsString, so it doesn’t actually validate ISO date format despite the comment. Either:

    • Update the comment to describe it as a plain string parser, or
    • Use a small custom parser (e.g., via createParser with a simple format check) if you want real ISO validation.
  • For parseAsFilters / parseAsStringArray, the custom parse functions only cast the parsed JSON, so they add type information but no runtime shape validation. That’s fine if these params are only written by your own UI, but if they can be user‑controlled you might want minimal checks (e.g., Array.isArray and basic element checks) before casting.


12-24: Reduce drift risk for enum‑like value lists

The hard‑coded lists in timeBucketValues, statTypeValues, and wellKnownValues must stay in sync with their corresponding domain types (e.g., StatType in client/src/lib/store.ts line 5). It’s easy for these to diverge over time when new values are added.

Not strictly required, but consider either:

  • Centralizing these arrays alongside the type definitions and importing them here, or
  • Adding small tests that assert the arrays cover all expected values.

That will help catch mismatches early as the set of allowed values evolves.

Also applies to: 27-37, 52-72

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba9a3a7 and 8df9f29.

📒 Files selected for processing (10)
  • client/src/app/[site]/components/Sidebar/SiteSelector.tsx (3 hunks)
  • client/src/app/[site]/gsc/select-property/page.tsx (2 hunks)
  • client/src/app/[site]/utils.ts (1 hunks)
  • client/src/app/as/callback/page.tsx (2 hunks)
  • client/src/app/auth/subscription/success/page.tsx (0 hunks)
  • client/src/app/invitation/page.tsx (3 hunks)
  • client/src/app/signup/page.tsx (2 hunks)
  • client/src/components/SiteSettings/GSCManager.tsx (1 hunks)
  • client/src/lib/parsers.ts (1 hunks)
  • client/src/lib/urlParams.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • client/src/app/auth/subscription/success/page.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
client/src/lib/urlParams.ts (2)
client/src/lib/parsers.ts (1)
  • analyticsParsers (98-118)
client/src/components/DateSelector/types.ts (1)
  • Time (44-44)
client/src/app/[site]/utils.ts (1)
client/src/api/admin/sites.ts (1)
  • useCurrentSite (198-207)
client/src/components/SiteSettings/GSCManager.tsx (1)
client/src/api/gsc/useGSCConnection.ts (3)
  • useGSCConnection (13-23)
  • useConnectGSC (28-39)
  • useDisconnectGSC (44-62)
client/src/lib/parsers.ts (1)
client/src/lib/store.ts (1)
  • StatType (6-6)
⏰ 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: Build Client Images (ubuntu-latest, linux/amd64)
  • GitHub Check: Build Client Images (ubuntu-24.04-arm, linux/arm64)
🔇 Additional comments (8)
client/src/app/[site]/components/Sidebar/SiteSelector.tsx (3)

2-2: Import changes look correct.

The removal of useSearchParams from the import statement aligns with the refactoring approach in this file.


59-60: Verify consistency with PR migration strategy.

The code correctly preserves query parameters during navigation and is SSR-safe due to the typeof window !== "undefined" guard on line 31. However, this approach using window.location.search differs from the PR's stated objective of migrating to the nuqs library's useQueryState.

Please confirm whether this component intentionally uses a different approach, or if it should also be migrated to nuqs for consistency.


103-104: Add window check to prevent potential SSR errors.

Accessing window.location.search without checking if window exists could cause runtime errors during server-side rendering or pre-rendering in Next.js.

Apply this diff to add a safe guard:

-                    const queryString = window.location.search;
+                    const queryString = typeof window !== "undefined" ? window.location.search : "";
                    router.push(queryString ? `${newPath}${queryString}` : newPath);

Alternatively, consider using Next.js's useSearchParams hook for consistency with Next.js best practices:

const searchParams = useSearchParams();
const queryString = searchParams.toString();
router.push(queryString ? `${newPath}?${queryString}` : newPath);

Run the following script to check if similar patterns exist elsewhere:

client/src/app/as/callback/page.tsx (2)

41-45: LGTM!

The useQueryStates usage is syntactically correct for reading URL query parameters.


18-18: Verify nuqs library compatibility with Next.js 15 and React 19.

Ensure that the version of nuqs being used is compatible with Next.js 15 and React 19, as this PR targets a codebase using these versions.

Run the following script to check the installed version and available updates:

Additionally, search the web for compatibility information:

client/src/app/[site]/utils.ts (1)

3-15: embed handling via parseAsBoolean keeps previous behavior intact

Switching from a string comparison to parseAsBoolean with if (embed && subscription?.planName !== "free") preserves the intent of only treating embed=true as embeddable (and still blocking free plans). This looks consistent with the existing semantics.

client/src/lib/urlParams.ts (1)

191-233: Time → URL mapping and newParams construction look consistent

The newParams construction correctly:

  • Always writes timeMode, bucket, stat, and only includes filters when non‑empty.
  • Uses time.wellKnown to prefer compact presets and explicitly clears all date/past‑minutes fields in that case.
  • Falls back to explicit date/past‑minutes fields keyed by time.mode when there’s no wellKnown.

This matches the structure defined in analyticsParsers and should keep URL/state round‑tripping predictable.

client/src/lib/parsers.ts (1)

6-9: Centralized query param parsers look solid

Mapping individual parsers into invitationParsers, appSumoCallbackParsers, and analyticsParsers gives a clear, single place for URL parameter handling and should make future changes easier and less error‑prone. No issues from my side here.

Also applies to: 85-96, 98-118

@goldflag goldflag merged commit c96d21b into master Nov 20, 2025
6 of 8 checks passed
goldflag added a commit that referenced this pull request Nov 20, 2025
acvigue pushed a commit to acvigue/rybbit that referenced this pull request Nov 21, 2025
…o#707)

- Replaced `useSearchParams` with `useQueryState` from the `nuqs` library across multiple components for better state synchronization with URL parameters.
- Updated components to utilize new query state management, enhancing clarity and reducing reliance on direct URL manipulation.
- Improved overall code maintainability and readability by streamlining parameter handling logic.
acvigue pushed a commit to acvigue/rybbit that referenced this pull request Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants