feat: add useAuth0Suspense hook for handling auth loading state with React 19+ - #1184
feat: add useAuth0Suspense hook for handling auth loading state with React 19+#1184gyaneshgouraw-okta wants to merge 2 commits into
Conversation
📝 WalkthroughWalkthrough
ChangesAuth0 Suspense integration
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Application
participant Auth0Provider
participant Auth0Client
participant useAuth0Suspense
participant Suspense
participant ErrorBoundary
Application->>Auth0Provider: render application
Auth0Provider->>Auth0Client: initialize Auth0
Application->>useAuth0Suspense: read authentication context
useAuth0Suspense->>Auth0Provider: read _initPromise
useAuth0Suspense-->>Suspense: suspend while initialization is pending
Auth0Client-->>Auth0Provider: resolve or reject initialization
Auth0Provider-->>useAuth0Suspense: provide initialized context or error
Suspense-->>Application: render authenticated content
ErrorBoundary-->>Application: render initialization error
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 `@src/use-auth0-suspense.tsx`:
- Around line 10-13: Update Auth0SuspenseContextInterface and the
useAuth0Suspense return destructuring to omit _initPromise alongside isLoading
and error, ensuring the internal field is excluded from both the public type and
runtime result.
- Line 1: Update the import in use-auth0-suspense.tsx to use a React namespace
import, and change the hook’s use invocation to React.use while preserving
useContext and the existing behavior. This avoids requiring a static named use
export from older supported React versions.
🪄 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: 1c9f4d54-bc0f-4e6e-8254-1c70f6a0094b
📒 Files selected for processing (7)
EXAMPLES.md__tests__/auth-provider.test.tsx__tests__/use-auth0-suspense.test.tsxsrc/auth0-context.tsxsrc/auth0-provider.tsxsrc/index.tsxsrc/use-auth0-suspense.tsx
| @@ -0,0 +1,59 @@ | |||
| import { use, useContext } from 'react'; | |||
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does importing a named export that is absent in an older CommonJS React version (e.g. use) via import { use } from 'react' trigger build errors/warnings under bundlers with strict export-presence checks (e.g. Create React App / webpack strictExportPresence)?
💡 Result:
Yes, importing a named export that does not exist in an older version of React (such as import { use } from 'react' in React 18 or earlier) will trigger a build error under bundlers configured with strict export-presence checks [1][2]. When strictExportPresence is enabled in Webpack—a configuration used by default in older versions of Create React App—a missing named export is elevated from a build-time warning to a hard compilation error [3][4][5]. ### Why this happens 1. Static Analysis: Modern bundlers like Webpack perform static analysis on your code to verify imports. When they encounter an import statement for a named export, they check the target module (in this case, the react package) to ensure that the export exists [5][6]. 2. Strict Presence Checks: When module.strictExportPresence is set to true, the bundler strictly enforces that all imported members must be present in the target module [3][7]. If a library (such as a router or UI library) uses a new API like use() that is not available in the installed version of React, the bundler identifies this discrepancy during the build process and halts compilation [1][2]. ### Common Scenarios * Create React App (CRA): Older versions of CRA specifically enabled strictExportPresence to ensure that developers were alerted to missing imports, which helps prevent runtime issues [4][5]. * Dependency Incompatibility: This issue is frequently encountered when upgrading third-party dependencies (like react-router v7) that utilize new features (like the React 19 use hook) while the project is still running an older version of React (e.g., React 18) [1][2]. ### How to Resolve * Upgrade React: The most direct fix is to upgrade react and react-dom to a version that supports the required exports (e.g., React 19 for the use hook) [1][8][9]. * Adjust Bundler Configuration: If an immediate upgrade is not possible, you can temporarily downgrade the error to a warning by adjusting your Webpack configuration [2]: javascript // In your webpack.config.js module: { parser: { javascript: { importExportsPresence: 'warn' // Or 'false' to suppress } } } Note that setting this to false or warn suppresses the build error but may lead to runtime undefined errors if the code attempts to call the missing import [5][2][10].
Citations:
- 1: Bug: Attempted import error: 'use' is not exported from 'react' (imported as 'React5'). react/react#33802
- 2: react-router@7.x breaks with React 18: "use is not exported from 'react'" remix-run/react-router#14020
- 3: Add strictExportPresence option webpack/webpack#4348
- 4: Missing export should be a compile error react/create-react-app#1559
- 5: webpack should not warn import usage, when import is unreachable (e.g. guarded by
ifstatement) webpack/webpack#14814 - 6: https://stackoverflow.com/questions/40071942/webpack-fails-silently-when-named-import-doesnt-exist
- 7: https://stackoverflow.com/questions/51705570/webpack-warn-when-import-is-undefined-not-declared
- 8: https://stackoverflow.com/questions/79046729/cannot-use-react-19-new-use-hook
- 9:
usenot found in React export with React18.2vercel/next.js#42548 - 10: https://stackoverflow.com/questions/55237524/make-webpack-fail-on-undefined-named-imports
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## package files"
git ls-files | rg '(^|/)(package\.json|tsconfig.*\.json|vite\.config\.[jt]s|webpack\.config\.[jt]s|rollup\.config\.[jt]s|babel\.config\.[jt]s|\.npmrc|pnpm-workspace\.yaml)$' || true
echo
echo "## relevant package.json react version mentions"
python3 - <<'PY'
import json, pathlib
for p in pathlib.Path('.').rglob('package.json'):
try:
data=json.loads(p.read_text())
except Exception as e:
continue
keys=set()
for sec in ('dependencies','devDependencies','peerDependencies','optionalDependencies'):
if isinstance(data.get(sec), dict):
keys.update(k for k,v in data[sec].items() if k=='react' or 'react' in k)
if keys:
print(p)
for sec in ('dependencies','devDependencies','peerDependencies','optionalDependencies'):
vals={k:data[sec][k] for k in keys if k in data.get(sec, {})}
if vals:
print(" ", sec, "=>", vals)
PY
echo
echo "## target file outline and contents"
file="$(git ls-files | rg '(^|/)use-auth0-suspense\.tsx$' | head -n1 || true)"
if [ -n "$file" ]; then
wc -l "$file"
ast-grep outline "$file" || true
echo
cat -n "$file"
fi
echo
echo "## named import usages of react/use"
rg -n "import \{[^}]*\buse\b[^\}]*\} from ['\"]react['\"]|React\.use|useHook|typeof use|strictExportPresence|importExportsPresence" . -g '!node_modules' -g '!dist' -g '!build' || trueRepository: auth0/auth0-react
Length of output: 11380
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## react package shape in node_modules"
python3 - <<'PY'
import json, pathlib
print("## react package.json")
try:
p=pathlib.Path("node_modules/react/package.json")
data=json.loads(p.read_text())
print(data.get("exports"))
print(data.get("module"))
print([k for k in data if k in ("exports","types","type","main","module","name","version")])
except Exception as e:
print("missing node_modules/react/package.json:", e)
print("\n## exports snippets")
for p in pathlib.Path("node_modules/react").glob("**"):
if p.is_file() and p.name in ("index.cjs","index.production.min.js","index.development.js","index.js","package.json"):
text=p.read_text(errors="ignore")
if "use" in text:
print("\n---", p, "---")
lines=text.splitlines()
for i,l in enumerate(lines,1):
if "use" in l.lower() and ("export" in l or "function" in l or "useContext" in l or "React.createElement" in l):
print(i,l)
txt="\n".join(lines[:220])
print(txt[-1500:])
PY
echo
echo "## test around React.use unavailable"
testf="$(git ls-files | rg '__tests__/use-auth0-suspense\.test\.tsx$' | head -n1 || true)"
if [ -n "$testf" ]; then
wc -l "$testf"
sed -n '80,140p' "$testf" | cat -n
fiRepository: auth0/auth0-react
Length of output: 2849
Use React.use for React 16/17/18 compatibility.
import { use } from 'react' requires the static named export added in React 19. This hook is documented for React 19+, but the package still supports down to React 16.11 and tests cover unavailable use; consumers with older React can fail build-time named-export checks, so use a namespace import and call React.use to avoid breaking React 18 and older consumers for an optional feature.
🤖 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 `@src/use-auth0-suspense.tsx` at line 1, Update the import in
use-auth0-suspense.tsx to use a React namespace import, and change the hook’s
use invocation to React.use while preserving useContext and the existing
behavior. This avoids requiring a static named use export from older supported
React versions.
| export type Auth0SuspenseContextInterface<TUser extends User = User> = Omit< | ||
| Auth0ContextInterface<TUser>, | ||
| 'isLoading' | 'error' | ||
| >; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
_initPromise leaks into the public type and runtime return value.
The docstring promises the hook "returns the full useAuth0 interface minus isLoading ... and error", but Auth0SuspenseContextInterface's Omit list and the { isLoading, error, ...rest } destructure both leave out _initPromise. Since _initPromise is documented as internal/not-public-API on Auth0ContextInterface, it should not be exposed through this hook's type or return value either.
🔧 Proposed fix
export type Auth0SuspenseContextInterface<TUser extends User = User> = Omit<
Auth0ContextInterface<TUser>,
- 'isLoading' | 'error'
+ 'isLoading' | 'error' | '_initPromise'
>;- // eslint-disable-next-line `@typescript-eslint/no-unused-vars`
- const { isLoading, error, ...rest } = ctx;
+ // eslint-disable-next-line `@typescript-eslint/no-unused-vars`
+ const { isLoading, error, _initPromise, ...rest } = ctx;
return rest;Also applies to: 54-56
🤖 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 `@src/use-auth0-suspense.tsx` around lines 10 - 13, Update
Auth0SuspenseContextInterface and the useAuth0Suspense return destructuring to
omit _initPromise alongside isLoading and error, ensuring the internal field is
excluded from both the public type and runtime result.
Summary
Adds
useAuth0Suspense, a React 19-only hook that lets components read auth state declaratively: a<Suspense>boundary handles the loading state and a React Error Boundary handles initialization errors, removing the manualisLoading/errorchecks thatuseAuth0requires.The change is additive and non-breaking, the core path (
Auth0Provider,useAuth0) stays compatible with React 16.11–19,peerDependenciesis unchanged, and the new hook is gated at runtime so React 16–18 consumers get a clear error instead of a crash if they call it.Changes
Auth0Providernow creates a stable initialization promise that resolves when auth init succeeds and rejects (with the same error surfaced onerror) when it fails, exposed as an internal, unsupported context field.useAuth0Suspense()hook reads that promise via React 19'suse(): it suspends until auth is ready (so the nearest<Suspense>fallback renders) and re-throws init errors to the nearest Error Boundary.useAuth0interface minusisLoading(always resolved) anderror(thrown), including all auth methods (loginWithRedirect,logout,getAccessTokenSilently, …).React.useis unavailable, and a "must be used within an<Auth0Provider>" error when used outside a provider.Auth0SuspenseContextInterfacetype.Example
Testing
npm test- full suite passing at 100% coverage.Auth0Providertests extended to verify the init promise resolves on success, rejects on failure, and keeps a stable reference across re-renders.