Skip to content

Fix duplicate /users/me requests during console sign-in - #40

Merged
JayaShakthi97 merged 1 commit into
thunder-id:mainfrom
PasinduYeshan:fix/4115-duplicate-users-me
Jul 31, 2026
Merged

Fix duplicate /users/me requests during console sign-in#40
JayaShakthi97 merged 1 commit into
thunder-id:mainfrom
PasinduYeshan:fix/4115-duplicate-users-me

Conversation

@PasinduYeshan

@PasinduYeshan PasinduYeshan commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Fixes thunder-id/thunderid#4115 — a single console sign-in issued ~10 GET /users/me (measured: 9 and 12) instead of 1.

Two independent defects, measured separately at runtime.

1. ProtectedRoute retried its own render, re-issuing sign-in each time

signIn() was called from the render body, which updates ThunderIDProvider state during render:

Cannot update a component (ThunderIDProvider) while rendering a different component (ProtectedRoute)

Worse, the throw new ThunderIDRuntimeError('ProtectedRoute misconfiguration.', ...) sat after the if (!isSignedIn) block and no branch inside returned. Since isSignedIn is already known false at that point, it fired on every unauthenticated render, including ones the component had just handled correctly (signInUrl, onSignIn, and the default sign-in all fell through to it).

React discarded each throw and re-rendered the root synchronously (11× There was an error during concurrent rendering but React was able to recover), and every retry re-ran the render-phase signIn(). That loop is the request burst. It self-terminated once auth state settled, which is why the bug was invisible except in the network tab.

Decisions:

  • Sign-in moved into useEffect, guarded by a ref so it fires once per unauthenticated state. The ref resets when the state leaves "unauthenticated", so a later sign-out re-initiates rather than rendering the loader forever.
  • The unconditional throw is removed, not returned-around. Every state now returns explicitly (isLoading→loader, isSignedIn→children, fallback, redirectTo<Navigate>, otherwise initiate sign-in and render the loader), so ProtectedRoute-Misconfiguration-001 is unreachable by construction. It never detected a misconfiguration; it was a missing return.
  • ProtectedRoute-SignInError-001 is kept but reported, not thrown. It previously lived inside an async IIFE, so it could never reach React and became an unhandled rejection. It is now constructed with the same code and origin, carries the original error as details, and goes to the package logger — matching how CallbackRoute already handles async auth failures.

2. ThunderIDProvider resumed the session twice per mount

The mount effect took isAlreadySignedIn = await client.isSignedIn(), ran resumeSession() if true, then unconditionally scheduled the refresh, re-checked isSignedIn(), and ran resumeSession() again. The pre-check can only be true when the token is valid — a state in which the re-check is also trivially true — so an already-signed-in load always paid 2 /users/me and 3 startAutoRefreshToken().

Decisions:

  • Auto-refresh is not removed; it now runs once instead of three times. startAutoRefreshToken() is still called unconditionally on every mount, before the sign-in check. Keeping it there preserves the case it exists for: when the access token expired but the refresh token is still valid, startAutoRefreshToken() refreshes immediately (timeUntilRefresh <= 0), so the isSignedIn() check that follows observes the refreshed state. A regression test covers exactly this ordering.
  • Only the redundant isSignedIn() pre-check was dropped, along with the duplicate resumeSession() it guarded. The second check is retained because it is the one that can observe a silent refresh. This also matches @thunderid/vue, which has always done this as a single if.
  • updateSession() is now single-flight. Concurrent callers share the in-flight promise. Deliberately not a cache: the ref clears on settle, so a later call (e.g. revalidation after a profile update) fetches again.

Scope decisions

  • Fixed in the SDK, not in the product. Console has 33 prop-less <ProtectedRoute> elements and Gate mounts its own provider; making the prop-less usage correct fixes both, instead of editing 33 call sites with loader/fallback and relying on convention for new routes.
  • One change was implemented, measured, and then dropped. Making signIn() await the session sync before clearing the loading state closed the window where isLoading === false while isSignedIn === false. Once ProtectedRoute no longer loops on that window, it made no difference to the request count, so it is not included. The window itself still exists and is worth a separate look.

Verification

Measured against a real console sign-in (Playwright, counting requests), building each variant separately:

Variant Fresh login Reload
Before 9, then 12 2
ProtectedRoute fix only 2 2
+ provider mount-path collapse 2 1
+ updateSession() single-flight 1 1

Final state re-measured 3× at 1/1. POST /oauth2/token unchanged (1 on login, 0 on reload). All render-phase errors gone.

Tests: 11 new for ProtectedRoute (7 fail without this change) and 5 new for ThunderIDProvider. pnpm build and pnpm format:check pass; lint findings on the touched files are unchanged or one fewer; the 2 TokenCallback failures and the nuxt vue-tsc tooling gap are pre-existing on main.

Supporting changes: re-export createPackageComponentLogger from @thunderid/react (react-router depends only on react and it was not re-exported); add @testing-library/react devDep, the missing *.tsx entries in tsconfig.spec.json, and a .vitest-attachments/ ignore to react-router — it had no component-test setup before.

Summary by CodeRabbit

  • Bug Fixes
    • Updated protected-route sign-in to begin after rendering (effect-driven), only once per unauthenticated episode, and to avoid duplicate sign-in during auth transitions.
    • Converted sign-in failures into logged runtime errors without breaking the route; preserved loader/redirect behavior and option forwarding.
    • Coalesced concurrent session-refresh requests into a single in-flight update and improved mount-time session bootstrap ordering.
  • Tests
    • Expanded protected-route and provider test coverage, including redirects, retries, error reporting, and concurrent single-flight behavior.
  • Chores
    • Updated test configuration to ignore Vitest browser artifacts and broaden spec matching.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PasinduYeshan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d418ea11-8ca2-42e0-9d57-b5d86e1b617b

📥 Commits

Reviewing files that changed from the base of the PR and between e908cc6 and 0211c18.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • packages/react-router/.gitignore
  • packages/react-router/package.json
  • packages/react-router/src/components/ProtectedRoute.tsx
  • packages/react-router/src/components/__tests__/ProtectedRoute.test.tsx
  • packages/react-router/tsconfig.spec.json
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx
  • packages/react/src/contexts/ThunderID/__tests__/ThunderIDProvider.test.tsx
📝 Walkthrough

Walkthrough

ProtectedRoute now performs guarded effect-based sign-in with navigation, custom handlers, option forwarding, and logged failures. ThunderIDProvider coalesces concurrent session updates and adjusts mount-time session restoration. Tests and package configuration cover both flows.

Changes

ProtectedRoute sign-in flow

Layer / File(s) Summary
Effect-driven sign-in and validation
packages/react-router/src/components/ProtectedRoute.tsx, packages/react-router/src/components/__tests__/ProtectedRoute.test.tsx, packages/react-router/package.json, packages/react-router/tsconfig.spec.json, packages/react-router/.gitignore
ProtectedRoute initiates sign-in from an effect, supports redirects and custom handlers, forwards options, prevents repeated initiation within an episode, logs runtime errors, and renders the loader while unauthenticated. Tests and package configuration cover these behaviors.

Session refresh coordination

Layer / File(s) Summary
Single-flight session updates
packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx, packages/react/src/contexts/ThunderID/__tests__/ThunderIDProvider.test.tsx
ThunderIDProvider coalesces concurrent session updates, performs mount-time auto-refresh before checking sign-in state, and restores the profile through the shared update path. Tests cover bootstrap ordering and concurrent update handling.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProtectedRoute
  participant ThunderIDState
  participant navigate
  participant signIn
  participant logger
  ProtectedRoute->>ThunderIDState: read authentication state
  ProtectedRoute->>navigate: navigate to redirect or signInUrl
  ProtectedRoute->>signIn: initiate default sign-in with merged options
  signIn-->>ProtectedRoute: resolve or reject
  ProtectedRoute->>logger: log ThunderIDRuntimeError on failure
Loading
sequenceDiagram
  participant ThunderIDProvider
  participant ThunderIDReactClient
  participant updateSession
  participant getUsersMe
  ThunderIDProvider->>ThunderIDReactClient: startAutoRefreshToken
  ThunderIDProvider->>ThunderIDReactClient: check isSignedIn
  ThunderIDProvider->>updateSession: request profile update
  updateSession->>getUsersMe: fetch user profile
  ThunderIDProvider->>updateSession: issue concurrent request
  updateSession-->>ThunderIDProvider: reuse pending promise
Loading

Suggested reviewers: brionmario

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: duplicate /users/me requests during console sign-in.
Description check ✅ Passed The description explains the problem, root causes, implementation approach, and verification, though it doesn't follow the template headings exactly.
Linked Issues check ✅ Passed The changes address issue #4115 by removing duplicate sign-in/session-refresh paths and adding tests that verify fewer /users/me calls.
Out of Scope Changes check ✅ Passed The extra dependency, tsconfig, and ignore-file updates are supporting changes for the auth/test work and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-router/src/components/ProtectedRoute.tsx`:
- Around line 169-228: Update the guard reset logic in the ProtectedRoute
sign-in useEffect so isLoading changes do not clear hasInitiatedSignInRef while
sign-in remains needed; reset it only when isSignedIn, fallback, or redirectTo
indicates sign-in is no longer required. In the signIn rejection handler, clear
hasInitiatedSignInRef before logging the error so a later recovery can retry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fff3a076-f714-4b99-8920-6df21515c9ad

📥 Commits

Reviewing files that changed from the base of the PR and between 0d057c0 and 016c3cf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • packages/react-router/.gitignore
  • packages/react-router/package.json
  • packages/react-router/src/components/ProtectedRoute.tsx
  • packages/react-router/src/components/__tests__/ProtectedRoute.test.tsx
  • packages/react-router/tsconfig.spec.json
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx
  • packages/react/src/contexts/ThunderID/__tests__/ThunderIDProvider.test.tsx
  • packages/react/src/index.ts

Comment thread packages/react-router/src/components/ProtectedRoute.tsx
@PasinduYeshan
PasinduYeshan force-pushed the fix/4115-duplicate-users-me branch from 4222022 to b13c2de Compare July 28, 2026 04:28
@PasinduYeshan

Copy link
Copy Markdown
Contributor Author

After the fix:
image

mergedParams && Object.keys(mergedParams).length > 0 ? {params: mergedParams} : undefined,
).catch((error: unknown) => {
// Release the guard so a later render can retry instead of being blocked forever.
hasInitiatedSignInRef.current = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this cause re-render and re-fire sign-in after a failure? If so, will it end up in an infinite loop?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodeRabbit WDYT?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PasinduYeshan, yes—this is a valid concern.

The rejection handler releases hasInitiatedSignInRef, so any subsequent render while the user remains unauthenticated and isLoading is false will re-enter the effect and call signIn() again. The .catch() itself does not trigger a render, but a typical failed sign-in updates provider loading/session state; that render is enough to retry. If the failure is persistent (for example, network/configuration failure), this can become a sign-in/logging loop.

Consider keeping the guard set after failure and exposing an explicit retry path, or recording the failed attempt and only retrying after a meaningful state/configuration change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As CodeRabbit mentioned, while resetting the guard doesn't itself trigger a re-render, a failed signIn() can update the provider state, causing the effect to run again and potentially retry indefinitely if the failure persists.

To address that, I'm thinking of adding a bounded retry mechanism (e.g. max 5 attempts). That would allow recovery from transient failures while preventing unbounded retries on persistent failures. Does that sound like a reasonable approach?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for additional context, this retry path is a consequence of moving signIn() into useEffect to fix the original issue. Previously, signIn() was invoked during render, which could lead to repeated render-phase invocations and the synchronous request burst described in the PR. With the effect + guard, that issue is eliminated.

The remaining concern is only around persistent asynchronous failures if we explicitly re-arm the guard in .catch(). I think adding a bounded retry limit (e.g. 5 attempts) strikes a good balance—it still recovers from transient failures while preventing indefinite retries if sign-in continues to fail.

@CodeRabbit verify.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PasinduYeshan, verified. Your diagnosis is correct: moving signIn() into the effect plus the guard eliminates the original render-phase retry/request burst. The remaining loop is specifically caused by re-arming the guard after an asynchronous rejection and then receiving another render while still unauthenticated.

A bounded retry policy is a reasonable fix, with two important details:

  • Track attempts in a ref and reset it only when the authentication episode genuinely ends—for example, when needsSignIn becomes false (signed in, fallback, or redirect path).
  • Schedule retries with a delay/backoff rather than immediately releasing the guard for the next render. Otherwise, five retries can still produce a short burst of failed calls and logs.

After the maximum is reached, keep the guard closed and leave the loader/error state stable (or expose an explicit consumer-controlled retry). That preserves transient-failure recovery without recreating an unbounded request loop.


✏️ Learnings added
Learnt from: PasinduYeshan
Repo: thunder-id/javascript-sdks PR: 40
File: packages/react-router/src/components/ProtectedRoute.tsx:211-211
Timestamp: 2026-07-28T10:57:23.042Z
Learning: In `packages/react-router/src/components/ProtectedRoute.tsx`, moving `signIn()` from render into a guarded `useEffect` prevents the render-phase retry burst that caused duplicate authentication requests. If the effect re-arms its sign-in guard after an asynchronous `signIn()` rejection, retries must be bounded and preferably delayed/backed off; reset the retry state only when the current unauthenticated sign-in episode ends.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the retry mechanism because the pre-PR behavior did not provide an intentional or reliable retry policy. Any apparent recovery came from duplicate sign-in calls caused by rerenders or React recovery, rather than a controlled retry flow.

Without an explicit requirement for SDK-owned transient-failure recovery, a zero-retry approach is simpler and safer. It prevents render-driven request loops, avoids additional timer and state-management complexity, and leaves retry decisions to an explicit user action or higher-level application logic.
@CodeRabbit

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PasinduYeshan, verified. The zero-retry approach resolves the reported loop risk.

After a failed signIn() or onSignIn(), ProtectedRoute logs the error and keeps hasInitiatedSignInRef set. Later renders in the same unauthenticated episode cannot start another automatic sign-in attempt. The ref resets only when needsSignIn becomes false.

This avoids an implicit SDK retry policy and preserves control for the application to provide an explicit retry action when needed.


✏️ Learnings added
Learnt from: PasinduYeshan
Repo: thunder-id/javascript-sdks PR: 40
File: packages/react-router/src/components/ProtectedRoute.tsx:0-0
Timestamp: 2026-07-31T04:15:47.943Z
Learning: In `packages/react-router/src/components/ProtectedRoute.tsx`, failed automatic sign-in attempts use a zero-retry policy: `hasInitiatedSignInRef` remains set after a `signIn()` or `onSignIn()` failure and resets only when `needsSignIn` becomes false. This prevents render- or effect-driven retry loops. Applications must implement any explicit retry behavior.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

JayaShakthi97
JayaShakthi97 previously approved these changes Jul 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx (1)

162-163: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Log the swallowed session-update error.

The outer catch only has a TODO and never logs, so any failure in getDecodedIdToken()/profile hydration during performSessionUpdate() is completely invisible — updateSession() will resolve normally either way. logger is already used a few lines above (line 149); reuse it here.

🪵 Proposed fix
     } catch (error) {
-      // TODO: Add an error log.
+      logger.error('Failed to update session.', error);
     } finally {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx` around lines 162
- 163, Update the outer catch in performSessionUpdate to log the caught
session-update error through the existing logger, replacing the TODO while
preserving updateSession’s current resolution behavior.
🧹 Nitpick comments (1)
packages/react-router/src/components/ProtectedRoute.tsx (1)

264-271: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider surfacing terminal sign-in failure to the host app.

Once MAX_SIGN_IN_ATTEMPTS is hit, ProtectedRoute keeps rendering loader indefinitely with no consumer-visible signal beyond the internal logger — a persistent misconfiguration (e.g. bad signInUrl/network outage) looks like an infinite spinner to the end user. An optional onSignInError callback (or similar) would let host apps swap in an error UI instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-router/src/components/ProtectedRoute.tsx` around lines 264 -
271, The terminal retry branch in ProtectedRoute should notify the host app when
sign-in attempts reach MAX_SIGN_IN_ATTEMPTS instead of only logging and leaving
the loader visible indefinitely. Add an optional onSignInError-style callback to
the component’s public API, invoke it from the sign-in failure path at the retry
limit with the relevant error, and preserve the current loader behavior when no
callback is supplied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx`:
- Around line 162-163: Update the outer catch in performSessionUpdate to log the
caught session-update error through the existing logger, replacing the TODO
while preserving updateSession’s current resolution behavior.

---

Nitpick comments:
In `@packages/react-router/src/components/ProtectedRoute.tsx`:
- Around line 264-271: The terminal retry branch in ProtectedRoute should notify
the host app when sign-in attempts reach MAX_SIGN_IN_ATTEMPTS instead of only
logging and leaving the loader visible indefinitely. Add an optional
onSignInError-style callback to the component’s public API, invoke it from the
sign-in failure path at the retry limit with the relevant error, and preserve
the current loader behavior when no callback is supplied.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a726a68f-3dbd-4384-b925-bec3a2749a60

📥 Commits

Reviewing files that changed from the base of the PR and between 4222022 and 638045a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • packages/react-router/.gitignore
  • packages/react-router/package.json
  • packages/react-router/src/components/ProtectedRoute.tsx
  • packages/react-router/src/components/__tests__/ProtectedRoute.test.tsx
  • packages/react-router/tsconfig.spec.json
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx
  • packages/react/src/contexts/ThunderID/__tests__/ThunderIDProvider.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-router/tsconfig.spec.json

@PasinduYeshan
PasinduYeshan force-pushed the fix/4115-duplicate-users-me branch 2 times, most recently from e908cc6 to 53d37ee Compare July 30, 2026 16:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-router/src/components/__tests__/ProtectedRoute.test.tsx`:
- Around line 278-283: Update the test around the renderRoute() call to capture
its rerender function, then invoke rerender after each mocks.state.isLoading
assignment so ProtectedRoute reevaluates the changed state before asserting
signIn call counts. Preserve the existing expectation that toggling loading does
not re-arm the failed guard.

In `@packages/react-router/src/components/ProtectedRoute.tsx`:
- Around line 55-58: Update the onSignIn prop type to allow void or
Promise<void>, then in the ProtectedRoute sign-in effect invoke custom handlers
through a Promise.resolve().then chain and attach the existing sign-in error
logger/handling in catch, matching the default sign-in path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a05f054-cdff-4e11-acf9-22096601bec0

📥 Commits

Reviewing files that changed from the base of the PR and between 638045a and e908cc6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/react-router/package.json
  • packages/react-router/src/components/ProtectedRoute.tsx
  • packages/react-router/src/components/__tests__/ProtectedRoute.test.tsx

Comment thread packages/react-router/src/components/ProtectedRoute.tsx Outdated
Initiate sign-in from an effect instead of render, and guard it with a
ref so it only fires once per unauthenticated episode. On failure the
guard stays closed until the episode genuinely ends (sign-in, fallback,
or redirect), instead of releasing immediately and letting a re-render
retry it.

Also stop re-exporting createPackageComponentLogger from
@thunderid/react's public API; consumers that need it (react-router,
matching vue) import it from @thunderid/browser directly.

Fixes thunder-id/thunderid#4115
@PasinduYeshan
PasinduYeshan force-pushed the fix/4115-duplicate-users-me branch from 53d37ee to 0211c18 Compare July 30, 2026 17:12
@JayaShakthi97
JayaShakthi97 merged commit ed5d367 into thunder-id:main Jul 31, 2026
3 checks passed
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.

Multiple 'GET /users/me' requests are sent when logging into the console

2 participants