Skip to content

feat: add support for invitationCode#213

Open
DanielRivers wants to merge 1 commit intomainfrom
feat/invitation_code
Open

feat: add support for invitationCode#213
DanielRivers wants to merge 1 commit intomainfrom
feat/invitation_code

Conversation

@DanielRivers
Copy link
Member

Explain your changes

Add support for invitations

Checklist

🛟 If you need help, consider asking for advice over in the Kinde community.

@DanielRivers DanielRivers requested review from a team as code owners December 2, 2025 13:03
@coderabbitai
Copy link

coderabbitai bot commented Dec 2, 2025

Walkthrough

Modified KindeProvider.tsx to add synchronous invitation-code handling from URL parameters. The change introduces ref-based state management to coordinate invitation-based login redirects, with conditional child rendering and early-exit initialization guards to isolate the invitation flow from normal authentication.

Changes

Cohort / File(s) Summary
Invitation-code redirect flow
src/state/KindeProvider.tsx
Adds ref-based invitation_code parsing and storage; introduces isRedirectingRef, redirectInitiatedRef, and loginRef to manage redirect coordination; implements useEffect to trigger login with prompt=create when invitation_code exists; conditionally prevents child rendering during active redirect; updates initialization logic with early-return guard when redirect is in progress.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Ref management correctness: Verify initialization, lifecycle, and proper updates of isRedirectingRef, redirectInitiatedRef, invitationCodeRef, and loginRef
  • useEffect logic and dependencies: Ensure correct trigger conditions, dependency array completeness, and error handling for the invitation redirect flow
  • Render conditionals: Confirm child rendering prevention is properly guarded and that forceChildrenRender override works as intended
  • Init flow interaction: Validate that early-return logic during redirect doesn't break normal authentication initialization or create state inconsistencies

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add support for invitationCode' directly matches the main changeset focus of adding invitation code handling functionality.
Description check ✅ Passed The description 'Add support for invitations' relates to the changeset which implements invitation code functionality in the KindeProvider.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 feat/invitation_code

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

@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 (4)
src/state/KindeProvider.tsx (4)

228-234: URL parsing runs on every render.

new URLSearchParams(window.location.search) executes on every render, even though the refs only initialize once. Consider memoizing this or using a lazy initialization pattern:

-  // Check for invitation_code synchronously before any hooks/rendering
-  const params = new URLSearchParams(window.location.search);
-  const hasInvitationCode = params.has("invitation_code");
-  const invitationCodeRef = useRef<string | null>(
-    hasInvitationCode ? params.get("invitation_code") : null,
-  );
-  const isRedirectingRef = useRef(hasInvitationCode);
+  // Check for invitation_code synchronously before any hooks/rendering
+  const invitationCodeRef = useRef<string | null>(null);
+  const isRedirectingRef = useRef(false);
+  
+  // Initialize refs only once
+  if (invitationCodeRef.current === null && isRedirectingRef.current === false) {
+    const params = new URLSearchParams(window.location.search);
+    if (params.has("invitation_code")) {
+      invitationCodeRef.current = params.get("invitation_code");
+      isRedirectingRef.current = true;
+    }
+  }

Alternatively, use a dedicated initialization ref to track whether parsing has been done.


375-376: loginRef is assigned but never read.

loginRef is assigned at line 436 but never used elsewhere. The useEffect at line 439 directly uses the login callback from the dependency array instead of loginRef.current. Either remove the unused ref or use it for the intended immediate access pattern.

If the ref is no longer needed:

   const initRef = useRef(false);
-  const loginRef = useRef<typeof login | null>(null);
   const redirectInitiatedRef = useRef(false);

And remove the assignment at lines 435-436.


439-456: Consider invoking onError callback on invitation redirect failure.

The catch block logs the error but doesn't notify the application via the onError callback. This could leave users on a blank screen (since children aren't rendered during redirect) without understanding what went wrong.

       }).catch((error) => {
         console.error("Error processing invitation code:", error);
+        mergedCallbacks.onError?.(
+          {
+            error: "ERR_INVITATION_REDIRECT",
+            errorDescription: String(error),
+          },
+          {},
+          {} as KindeContextProps,
+        );
         isRedirectingRef.current = false;
         redirectInitiatedRef.current = false;
       });

Also note: the login && check on line 443 is always true since login is a stable useCallback reference.


788-793: Unused variable declaration before early return.

The params variable declared at line 788 is unused when isRedirectingRef.current is true (the early return at line 792). Additionally, it shadows the params variable from line 229. Consider removing this redundant parsing or moving it after the guard clause:

   const init = useCallback(async () => {
     if (initRef.current) return;
     try {
-      const params = new URLSearchParams(window.location.search);
-
       // Skip initialization if redirecting for invitation (handled in useEffect above)
       if (isRedirectingRef.current) {
         return;
       }

+      const params = new URLSearchParams(window.location.search);
+
       try {
         initRef.current = true;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 256ae19 and a22deb3.

⛔ Files ignored due to path filters (1)
  • package.json is excluded by !**/*.json
📒 Files selected for processing (1)
  • src/state/KindeProvider.tsx (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-05T12:17:11.365Z
Learnt from: DanielRivers
Repo: kinde-oss/kinde-auth-react PR: 173
File: src/state/KindeProvider.tsx:584-585
Timestamp: 2025-09-05T12:17:11.365Z
Learning: In Kinde auth React applications, the callback URL (redirectUri) is always absolute because it's required for hosted auth to work properly.

Applied to files:

  • src/state/KindeProvider.tsx
🔇 Additional comments (1)
src/state/KindeProvider.tsx (1)

890-893: LGTM!

The conditional render suppression correctly prevents rendering children during the invitation redirect flow while respecting the forceChildrenRender escape hatch.

Copy link
Contributor

@dtoxvanilla1991 dtoxvanilla1991 left a comment

Choose a reason for hiding this comment

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

great stuff. Left my thoughts.


// Skip initialization if redirecting for invitation (handled in useEffect above)
if (isRedirectingRef.current) {
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

This early-return skips init() entirely while redirecting (comment explains why of course). Combined with ref-based flagging though, a failed invitation redirect won’t automatically re-run init later, leaving the SDK permanently uninitialized. Thought: redirecting flag in state + ensure init runs once redirecting becomes false. Or is failed invitation link never an option?

};
}, [init]);

// Don't render children if redirecting for invitation
Copy link
Contributor

Choose a reason for hiding this comment

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

Early return of an empty fragment while redirecting is OK only if there’s a guaranteed rerender/unblock path. I believe, as implemented, the unblock path mutates a ref (no rerender), so this can dead-end into a blank screen. Should we do state-driven gating?

"private": false,
"dependencies": {
"@kinde/js-utils": "0.29.0"
"@kinde/js-utils": "0.29.1-5"
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to get pnpm-lock.yaml updated accordingly in the PR, and ok to pin a prerelease in a public SDK release line?

}: KindeProviderProps) => {
const mergedCallbacks = { ...defaultCallbacks, ...callbacks };
// Check for invitation_code synchronously before any hooks/rendering
const params = new URLSearchParams(window.location.search);
Copy link
Contributor

Choose a reason for hiding this comment

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

This reads window.location.search during render. Now, we know and aim for this to always be used on the client-side since we need to have access to window and all other browsers' APIs. That said, I think there are several things to consider:

  • Tests/build tooling can evaluate components outside a real browser (Vitest, Storybook, prerenderers). Guarding window during render would avoid brittle failures.
  • Doing new URLSearchParams(window.location.search) during render I believe also violates the “no side-effectful / environment-dependent reads during render” guideline for React libraries; it’s easy to make this deterministic via a lazy initializer + effect.

Thus maybe prefer typeof window !== 'undefined' guard + lazy useState initializer and do the redirect work inside useEffect. Lmk your thoughts 💭

Comment on lines +450 to +453
}).catch((error) => {
console.error("Error processing invitation code:", error);
isRedirectingRef.current = false;
redirectInitiatedRef.current = false;
Copy link
Contributor

@dtoxvanilla1991 dtoxvanilla1991 Jan 17, 2026

Choose a reason for hiding this comment

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

isRedirectingRef is used to gate redirect and later set to false on error, but refs don’t trigger re-render. If login() fails, consumers can get stuck rendering nothing (seeing later early-return). So once again, should we use state for redirecting so failure unblocks UI and allows init to continue. Or login() doesnt fail in this way?

);

// Store login in ref for immediate access
loginRef.current = login;
Copy link
Member

Choose a reason for hiding this comment

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

loginRef isn't being used except to store the value of login.

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.

4 participants