Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-days-tan.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Fixes issue where "prepare" API request would only fire once, preventing end users from receiving fresh otp codes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,19 +82,11 @@ export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) =>
.catch(err => handleError(err, [], card.setError));
};

useFetch(
shouldAvoidPrepare
? undefined
: () =>
signIn
?.prepareFirstFactor(props.factor)
.then(() => props.onFactorPrepare())
.catch(err => handleError(err, [], card.setError)),
cacheKey,
{
staleTime: 100,
},
);
useFetch(shouldAvoidPrepare ? undefined : () => signIn?.prepareFirstFactor(props.factor), cacheKey, {
staleTime: 100,
onSuccess: () => props.onFactorPrepare(),
onError: err => handleError(err, [], card.setError),
});

const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
signIn
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,9 +56,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand DownExpand Up@@ -91,9 +93,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123_whatsapp',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,19 +24,15 @@ export const SignUpEmailCodeCard = () => {

// TODO: Introduce a useMutation to handle mutating requests
useFetch(
shouldAvoidPrepare
? undefined
: () =>
signUp
.prepareEmailAddressVerification({ strategy: 'email_code' })
.catch(err => handleError(err, [], card.setError)),
shouldAvoidPrepare ? undefined : () => signUp.prepareEmailAddressVerification({ strategy: 'email_code' }),
{
name: 'prepare',
strategy: 'email_code',
number: signUp.emailAddress,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 34 to 36

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.

💡 Verification agent

🧩 Analysis chain

Verify onError safety with handleError rethrows; double-check staleTime

Same concern as the phone code card: confirm useFetch won’t surface unhandled exceptions if handleError rethrows, and that staleTime: 100 meets the intended dedupe window.

Run:

Expected:

  • onError invocation cannot crash the component tree.
  • Comment or test validates the 100ms window is sufficient for “prepare fires once” semantics.

🏁 Script executed:

#!/bin/bash# Confirm try/catch around user-provided onError and the internal error flow
rg -nP -C3 'onError\s*\(|try|catch|setState' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 285


🏁 Script executed:

#!/bin/bash# Inspect the beginning of the useFetch hook to locate onError invocation
sed -n '1,200p' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 5357


🏁 Script executed:

#!/bin/bash# Inspect the SignUpEmailCodeCard and SignUpPhoneCodeCard for staleTime and onError usage
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx
echo"---"
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx

Length of output: 5735


🏁 Script executed:

#!/bin/bash
rg -n "export (function|const) handleError" -n packages/clerk-js/src/ui/utils

Length of output: 209


Ensure onError callback safety and clarify staleTime usage

  • In useFetch (packages/clerk-js/src/ui/hooks/useFetch.ts), wrap the options.onError?.(e) invocation in a try/catch to prevent unhandled promise rejections if handleError throws.
  • Add a code comment or unit test to verify that the 100 ms staleTime (in SignUpEmailCodeCard.tsx and SignUpPhoneCodeCard.tsx) provides the intended dedupe window for “prepare fires once” semantics.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx around
lines 34-36, the options passed to useFetch set staleTime: 100 and onError: err
=> handleError(err, [], card.setError); update two things: in
packages/clerk-js/src/ui/hooks/useFetch.ts wrap the invocation of
options.onError?.(e) in a try/catch and log or swallow any errors to prevent
unhandled promise rejections if the provided handler (e.g., handleError) throws;
and in SignUpEmailCodeCard.tsx (and SignUpPhoneCodeCard.tsx) add a concise code
comment next to staleTime: 100 explaining that this 100ms window is the intended
de-duplication/deduping window for “prepare fires once” semantics or add a unit
test asserting that preparing twice within 100ms only triggers a single prepare
call.

);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,15 @@ export const SignUpPhoneCodeCard = withCardStateProvider(() => {
// because the verification is already created on the Start screen
shouldAvoidPrepare || isAlternativePhoneCodeProvider
? undefined
: () =>
signUp
.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined })
.catch(err => handleError(err, [], card.setError)),
: () => signUp.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined }),
{
name: 'signUp.preparePhoneNumberVerification',
strategy: 'phone_code',
number: signUp.phoneNumber,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 46 to 48

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.

💡 Verification agent

🧩 Analysis chain

Verify onError behavior with handleError rethrows; confirm staleTime=100ms is intentional

handleError rethrows unknown errors. Ensure useFetch guards against exceptions thrown inside onError so we don’t crash render. Also, validate that staleTime: 100 sufficiently dedupes initial rerenders without suppressing legitimate retries.

Run:

Expected:

  • useFetch calls onError inside a try/catch or otherwise prevents rethrow from breaking React render.
  • staleTime choice is deliberate (doc or comment) to coalesce only immediate duplicate prepares.

🏁 Script executed:

#!/bin/bash# Inspect onError invocation and error propagation in useFetch
rg -nP -C3 'function\s+useFetch|\bonError\b|\bcatch\s*\(' packages/clerk-js/src/ui/hooks/useFetch.ts
# Scan all useFetch callers for very short staleTime and generic names that could collide
rg -nP -C1 'useFetch\(' packages | sed -n '1,200p'

Length of output: 5044


🏁 Script executed:

#!/bin/bash# Search for handleError definition and usage across clerk-js
rg -nR 'handleError' -n packages/clerk-js

Length of output: 446


🏁 Script executed:

#!/bin/bash# Show useFetch implementation including error invocation context
rg -nP -C10 'function\s+useFetch' packages/clerk-js/src/ui/hooks/useFetch.ts
rg -nP -C10 '\.catch' packages/clerk-js/src/ui/hooks/useFetch.ts
# Show import and usage of handleError in SignUpPhoneCodeCard
sed -n '1,50p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx
# Search for handleError definition across the repo
rg -n 'handleError' -n .

Length of output: 25854


🏁 Script executed:

#!/bin/bash# Inspect staleTime handling in useFetch
rg -nP -C5 'staleTime' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 1310


🏁 Script executed:

#!/bin/bash
rg -nP -C10 'export const handleError' packages/clerk-js/src/ui/utils/errorHandler.ts

Length of output: 644


Guard onError invocation and confirm staleTime override

  • Wrap options.onError(e) in a try/catch inside useFetch’s .catch handler (packages/clerk-js/src/ui/hooks/useFetch.ts ~L170); since handleError can rethrow unknown errors, unhandled promise rejections may occur.
  • The 100 ms staleTime override matches other prepare hooks (e.g. SignInFactorOneCodeForm) and is intentional to dedupe rapid remounts without impacting legitimate retries.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx around
lines 46-48, guard the onError invocation by wrapping options.onError(e) in a
try/catch inside the useFetch .catch handler so any error thrown by handleError
(or other callbacks) does not create an unhandled promise rejection; if the
callback throws, catch it and log or forward to card.setError as a fallback.
Leave the staleTime: 100 override as-is (it is intentional to dedupe rapid
remounts).

);

Expand Down
6 changes: 5 additions & 1 deletion packages/clerk-js/src/ui/hooks/useFetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ export const useFetch = <K, T>(
options?: {
throttleTime?: number;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
staleTime?: number;
},
resourceId?: string,
Expand DownExpand Up@@ -166,14 +167,17 @@ export const useFetch = <K, T>(
}, waitTime);
}
})
.catch((e: Error) => {
.catch((e: Error | null) => {
setCache({
data: getCache()?.data ?? null,
isLoading: false,
isValidating: false,
error: e,
cachedAt: Date.now(),
});
if (e) {
options?.onError?.(e);
}
});
}, [serialize(params), setCache, getCache, revalidateCache]);

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-days-tan.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Fixes issue where "prepare" API request would only fire once, preventing end users from receiving fresh otp codes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,19 +82,11 @@ export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) =>
.catch(err => handleError(err, [], card.setError));
};

useFetch(
shouldAvoidPrepare
? undefined
: () =>
signIn
?.prepareFirstFactor(props.factor)
.then(() => props.onFactorPrepare())
.catch(err => handleError(err, [], card.setError)),
cacheKey,
{
staleTime: 100,
},
);
useFetch(shouldAvoidPrepare ? undefined : () => signIn?.prepareFirstFactor(props.factor), cacheKey, {
staleTime: 100,
onSuccess: () => props.onFactorPrepare(),
onError: err => handleError(err, [], card.setError),
});

const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
signIn
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,9 +56,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand DownExpand Up@@ -91,9 +93,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123_whatsapp',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,19 +24,15 @@ export const SignUpEmailCodeCard = () => {

// TODO: Introduce a useMutation to handle mutating requests
useFetch(
shouldAvoidPrepare
? undefined
: () =>
signUp
.prepareEmailAddressVerification({ strategy: 'email_code' })
.catch(err => handleError(err, [], card.setError)),
shouldAvoidPrepare ? undefined : () => signUp.prepareEmailAddressVerification({ strategy: 'email_code' }),
{
name: 'prepare',
strategy: 'email_code',
number: signUp.emailAddress,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 34 to 36

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.

💡 Verification agent

🧩 Analysis chain

Verify onError safety with handleError rethrows; double-check staleTime

Same concern as the phone code card: confirm useFetch won’t surface unhandled exceptions if handleError rethrows, and that staleTime: 100 meets the intended dedupe window.

Run:

Expected:

  • onError invocation cannot crash the component tree.
  • Comment or test validates the 100ms window is sufficient for “prepare fires once” semantics.

🏁 Script executed:

#!/bin/bash# Confirm try/catch around user-provided onError and the internal error flow
rg -nP -C3 'onError\s*\(|try|catch|setState' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 285


🏁 Script executed:

#!/bin/bash# Inspect the beginning of the useFetch hook to locate onError invocation
sed -n '1,200p' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 5357


🏁 Script executed:

#!/bin/bash# Inspect the SignUpEmailCodeCard and SignUpPhoneCodeCard for staleTime and onError usage
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx
echo"---"
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx

Length of output: 5735


🏁 Script executed:

#!/bin/bash
rg -n "export (function|const) handleError" -n packages/clerk-js/src/ui/utils

Length of output: 209


Ensure onError callback safety and clarify staleTime usage

  • In useFetch (packages/clerk-js/src/ui/hooks/useFetch.ts), wrap the options.onError?.(e) invocation in a try/catch to prevent unhandled promise rejections if handleError throws.
  • Add a code comment or unit test to verify that the 100 ms staleTime (in SignUpEmailCodeCard.tsx and SignUpPhoneCodeCard.tsx) provides the intended dedupe window for “prepare fires once” semantics.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx around
lines 34-36, the options passed to useFetch set staleTime: 100 and onError: err
=> handleError(err, [], card.setError); update two things: in
packages/clerk-js/src/ui/hooks/useFetch.ts wrap the invocation of
options.onError?.(e) in a try/catch and log or swallow any errors to prevent
unhandled promise rejections if the provided handler (e.g., handleError) throws;
and in SignUpEmailCodeCard.tsx (and SignUpPhoneCodeCard.tsx) add a concise code
comment next to staleTime: 100 explaining that this 100ms window is the intended
de-duplication/deduping window for “prepare fires once” semantics or add a unit
test asserting that preparing twice within 100ms only triggers a single prepare
call.

);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,15 @@ export const SignUpPhoneCodeCard = withCardStateProvider(() => {
// because the verification is already created on the Start screen
shouldAvoidPrepare || isAlternativePhoneCodeProvider
? undefined
: () =>
signUp
.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined })
.catch(err => handleError(err, [], card.setError)),
: () => signUp.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined }),
{
name: 'signUp.preparePhoneNumberVerification',
strategy: 'phone_code',
number: signUp.phoneNumber,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 46 to 48

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.

💡 Verification agent

🧩 Analysis chain

Verify onError behavior with handleError rethrows; confirm staleTime=100ms is intentional

handleError rethrows unknown errors. Ensure useFetch guards against exceptions thrown inside onError so we don’t crash render. Also, validate that staleTime: 100 sufficiently dedupes initial rerenders without suppressing legitimate retries.

Run:

Expected:

  • useFetch calls onError inside a try/catch or otherwise prevents rethrow from breaking React render.
  • staleTime choice is deliberate (doc or comment) to coalesce only immediate duplicate prepares.

🏁 Script executed:

#!/bin/bash# Inspect onError invocation and error propagation in useFetch
rg -nP -C3 'function\s+useFetch|\bonError\b|\bcatch\s*\(' packages/clerk-js/src/ui/hooks/useFetch.ts
# Scan all useFetch callers for very short staleTime and generic names that could collide
rg -nP -C1 'useFetch\(' packages | sed -n '1,200p'

Length of output: 5044


🏁 Script executed:

#!/bin/bash# Search for handleError definition and usage across clerk-js
rg -nR 'handleError' -n packages/clerk-js

Length of output: 446


🏁 Script executed:

#!/bin/bash# Show useFetch implementation including error invocation context
rg -nP -C10 'function\s+useFetch' packages/clerk-js/src/ui/hooks/useFetch.ts
rg -nP -C10 '\.catch' packages/clerk-js/src/ui/hooks/useFetch.ts
# Show import and usage of handleError in SignUpPhoneCodeCard
sed -n '1,50p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx
# Search for handleError definition across the repo
rg -n 'handleError' -n .

Length of output: 25854


🏁 Script executed:

#!/bin/bash# Inspect staleTime handling in useFetch
rg -nP -C5 'staleTime' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 1310


🏁 Script executed:

#!/bin/bash
rg -nP -C10 'export const handleError' packages/clerk-js/src/ui/utils/errorHandler.ts

Length of output: 644


Guard onError invocation and confirm staleTime override

  • Wrap options.onError(e) in a try/catch inside useFetch’s .catch handler (packages/clerk-js/src/ui/hooks/useFetch.ts ~L170); since handleError can rethrow unknown errors, unhandled promise rejections may occur.
  • The 100 ms staleTime override matches other prepare hooks (e.g. SignInFactorOneCodeForm) and is intentional to dedupe rapid remounts without impacting legitimate retries.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx around
lines 46-48, guard the onError invocation by wrapping options.onError(e) in a
try/catch inside the useFetch .catch handler so any error thrown by handleError
(or other callbacks) does not create an unhandled promise rejection; if the
callback throws, catch it and log or forward to card.setError as a fallback.
Leave the staleTime: 100 override as-is (it is intentional to dedupe rapid
remounts).

);

Expand Down
6 changes: 5 additions & 1 deletion packages/clerk-js/src/ui/hooks/useFetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ export const useFetch = <K, T>(
options?: {
throttleTime?: number;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
staleTime?: number;
},
resourceId?: string,
Expand DownExpand Up@@ -166,14 +167,17 @@ export const useFetch = <K, T>(
}, waitTime);
}
})
.catch((e: Error) => {
.catch((e: Error | null) => {
setCache({
data: getCache()?.data ?? null,
isLoading: false,
isValidating: false,
error: e,
cachedAt: Date.now(),
});
if (e) {
options?.onError?.(e);
}
});
}, [serialize(params), setCache, getCache, revalidateCache]);

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-days-tan.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Fixes issue where "prepare" API request would only fire once, preventing end users from receiving fresh otp codes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,19 +82,11 @@ export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) =>
.catch(err => handleError(err, [], card.setError));
};

useFetch(
shouldAvoidPrepare
? undefined
: () =>
signIn
?.prepareFirstFactor(props.factor)
.then(() => props.onFactorPrepare())
.catch(err => handleError(err, [], card.setError)),
cacheKey,
{
staleTime: 100,
},
);
useFetch(shouldAvoidPrepare ? undefined : () => signIn?.prepareFirstFactor(props.factor), cacheKey, {
staleTime: 100,
onSuccess: () => props.onFactorPrepare(),
onError: err => handleError(err, [], card.setError),
});

const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
signIn
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,9 +56,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand DownExpand Up@@ -91,9 +93,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123_whatsapp',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,19 +24,15 @@ export const SignUpEmailCodeCard = () => {

// TODO: Introduce a useMutation to handle mutating requests
useFetch(
shouldAvoidPrepare
? undefined
: () =>
signUp
.prepareEmailAddressVerification({ strategy: 'email_code' })
.catch(err => handleError(err, [], card.setError)),
shouldAvoidPrepare ? undefined : () => signUp.prepareEmailAddressVerification({ strategy: 'email_code' }),
{
name: 'prepare',
strategy: 'email_code',
number: signUp.emailAddress,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 34 to 36

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.

💡 Verification agent

🧩 Analysis chain

Verify onError safety with handleError rethrows; double-check staleTime

Same concern as the phone code card: confirm useFetch won’t surface unhandled exceptions if handleError rethrows, and that staleTime: 100 meets the intended dedupe window.

Run:

Expected:

  • onError invocation cannot crash the component tree.
  • Comment or test validates the 100ms window is sufficient for “prepare fires once” semantics.

🏁 Script executed:

#!/bin/bash# Confirm try/catch around user-provided onError and the internal error flow
rg -nP -C3 'onError\s*\(|try|catch|setState' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 285


🏁 Script executed:

#!/bin/bash# Inspect the beginning of the useFetch hook to locate onError invocation
sed -n '1,200p' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 5357


🏁 Script executed:

#!/bin/bash# Inspect the SignUpEmailCodeCard and SignUpPhoneCodeCard for staleTime and onError usage
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx
echo"---"
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx

Length of output: 5735


🏁 Script executed:

#!/bin/bash
rg -n "export (function|const) handleError" -n packages/clerk-js/src/ui/utils

Length of output: 209


Ensure onError callback safety and clarify staleTime usage

  • In useFetch (packages/clerk-js/src/ui/hooks/useFetch.ts), wrap the options.onError?.(e) invocation in a try/catch to prevent unhandled promise rejections if handleError throws.
  • Add a code comment or unit test to verify that the 100 ms staleTime (in SignUpEmailCodeCard.tsx and SignUpPhoneCodeCard.tsx) provides the intended dedupe window for “prepare fires once” semantics.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx around
lines 34-36, the options passed to useFetch set staleTime: 100 and onError: err
=> handleError(err, [], card.setError); update two things: in
packages/clerk-js/src/ui/hooks/useFetch.ts wrap the invocation of
options.onError?.(e) in a try/catch and log or swallow any errors to prevent
unhandled promise rejections if the provided handler (e.g., handleError) throws;
and in SignUpEmailCodeCard.tsx (and SignUpPhoneCodeCard.tsx) add a concise code
comment next to staleTime: 100 explaining that this 100ms window is the intended
de-duplication/deduping window for “prepare fires once” semantics or add a unit
test asserting that preparing twice within 100ms only triggers a single prepare
call.

);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,15 @@ export const SignUpPhoneCodeCard = withCardStateProvider(() => {
// because the verification is already created on the Start screen
shouldAvoidPrepare || isAlternativePhoneCodeProvider
? undefined
: () =>
signUp
.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined })
.catch(err => handleError(err, [], card.setError)),
: () => signUp.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined }),
{
name: 'signUp.preparePhoneNumberVerification',
strategy: 'phone_code',
number: signUp.phoneNumber,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 46 to 48

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.

💡 Verification agent

🧩 Analysis chain

Verify onError behavior with handleError rethrows; confirm staleTime=100ms is intentional

handleError rethrows unknown errors. Ensure useFetch guards against exceptions thrown inside onError so we don’t crash render. Also, validate that staleTime: 100 sufficiently dedupes initial rerenders without suppressing legitimate retries.

Run:

Expected:

  • useFetch calls onError inside a try/catch or otherwise prevents rethrow from breaking React render.
  • staleTime choice is deliberate (doc or comment) to coalesce only immediate duplicate prepares.

🏁 Script executed:

#!/bin/bash# Inspect onError invocation and error propagation in useFetch
rg -nP -C3 'function\s+useFetch|\bonError\b|\bcatch\s*\(' packages/clerk-js/src/ui/hooks/useFetch.ts
# Scan all useFetch callers for very short staleTime and generic names that could collide
rg -nP -C1 'useFetch\(' packages | sed -n '1,200p'

Length of output: 5044


🏁 Script executed:

#!/bin/bash# Search for handleError definition and usage across clerk-js
rg -nR 'handleError' -n packages/clerk-js

Length of output: 446


🏁 Script executed:

#!/bin/bash# Show useFetch implementation including error invocation context
rg -nP -C10 'function\s+useFetch' packages/clerk-js/src/ui/hooks/useFetch.ts
rg -nP -C10 '\.catch' packages/clerk-js/src/ui/hooks/useFetch.ts
# Show import and usage of handleError in SignUpPhoneCodeCard
sed -n '1,50p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx
# Search for handleError definition across the repo
rg -n 'handleError' -n .

Length of output: 25854


🏁 Script executed:

#!/bin/bash# Inspect staleTime handling in useFetch
rg -nP -C5 'staleTime' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 1310


🏁 Script executed:

#!/bin/bash
rg -nP -C10 'export const handleError' packages/clerk-js/src/ui/utils/errorHandler.ts

Length of output: 644


Guard onError invocation and confirm staleTime override

  • Wrap options.onError(e) in a try/catch inside useFetch’s .catch handler (packages/clerk-js/src/ui/hooks/useFetch.ts ~L170); since handleError can rethrow unknown errors, unhandled promise rejections may occur.
  • The 100 ms staleTime override matches other prepare hooks (e.g. SignInFactorOneCodeForm) and is intentional to dedupe rapid remounts without impacting legitimate retries.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx around
lines 46-48, guard the onError invocation by wrapping options.onError(e) in a
try/catch inside the useFetch .catch handler so any error thrown by handleError
(or other callbacks) does not create an unhandled promise rejection; if the
callback throws, catch it and log or forward to card.setError as a fallback.
Leave the staleTime: 100 override as-is (it is intentional to dedupe rapid
remounts).

);

Expand Down
6 changes: 5 additions & 1 deletion packages/clerk-js/src/ui/hooks/useFetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ export const useFetch = <K, T>(
options?: {
throttleTime?: number;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
staleTime?: number;
},
resourceId?: string,
Expand DownExpand Up@@ -166,14 +167,17 @@ export const useFetch = <K, T>(
}, waitTime);
}
})
.catch((e: Error) => {
.catch((e: Error | null) => {
setCache({
data: getCache()?.data ?? null,
isLoading: false,
isValidating: false,
error: e,
cachedAt: Date.now(),
});
if (e) {
options?.onError?.(e);
}
});
}, [serialize(params), setCache, getCache, revalidateCache]);

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-days-tan.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Fixes issue where "prepare" API request would only fire once, preventing end users from receiving fresh otp codes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,19 +82,11 @@ export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) =>
.catch(err => handleError(err, [], card.setError));
};

useFetch(
shouldAvoidPrepare
? undefined
: () =>
signIn
?.prepareFirstFactor(props.factor)
.then(() => props.onFactorPrepare())
.catch(err => handleError(err, [], card.setError)),
cacheKey,
{
staleTime: 100,
},
);
useFetch(shouldAvoidPrepare ? undefined : () => signIn?.prepareFirstFactor(props.factor), cacheKey, {
staleTime: 100,
onSuccess: () => props.onFactorPrepare(),
onError: err => handleError(err, [], card.setError),
});

const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
signIn
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,9 +56,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand DownExpand Up@@ -91,9 +93,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123_whatsapp',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,19 +24,15 @@ export const SignUpEmailCodeCard = () => {

// TODO: Introduce a useMutation to handle mutating requests
useFetch(
shouldAvoidPrepare
? undefined
: () =>
signUp
.prepareEmailAddressVerification({ strategy: 'email_code' })
.catch(err => handleError(err, [], card.setError)),
shouldAvoidPrepare ? undefined : () => signUp.prepareEmailAddressVerification({ strategy: 'email_code' }),
{
name: 'prepare',
strategy: 'email_code',
number: signUp.emailAddress,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 34 to 36

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.

💡 Verification agent

🧩 Analysis chain

Verify onError safety with handleError rethrows; double-check staleTime

Same concern as the phone code card: confirm useFetch won’t surface unhandled exceptions if handleError rethrows, and that staleTime: 100 meets the intended dedupe window.

Run:

Expected:

  • onError invocation cannot crash the component tree.
  • Comment or test validates the 100ms window is sufficient for “prepare fires once” semantics.

🏁 Script executed:

#!/bin/bash# Confirm try/catch around user-provided onError and the internal error flow
rg -nP -C3 'onError\s*\(|try|catch|setState' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 285


🏁 Script executed:

#!/bin/bash# Inspect the beginning of the useFetch hook to locate onError invocation
sed -n '1,200p' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 5357


🏁 Script executed:

#!/bin/bash# Inspect the SignUpEmailCodeCard and SignUpPhoneCodeCard for staleTime and onError usage
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx
echo"---"
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx

Length of output: 5735


🏁 Script executed:

#!/bin/bash
rg -n "export (function|const) handleError" -n packages/clerk-js/src/ui/utils

Length of output: 209


Ensure onError callback safety and clarify staleTime usage

  • In useFetch (packages/clerk-js/src/ui/hooks/useFetch.ts), wrap the options.onError?.(e) invocation in a try/catch to prevent unhandled promise rejections if handleError throws.
  • Add a code comment or unit test to verify that the 100 ms staleTime (in SignUpEmailCodeCard.tsx and SignUpPhoneCodeCard.tsx) provides the intended dedupe window for “prepare fires once” semantics.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx around
lines 34-36, the options passed to useFetch set staleTime: 100 and onError: err
=> handleError(err, [], card.setError); update two things: in
packages/clerk-js/src/ui/hooks/useFetch.ts wrap the invocation of
options.onError?.(e) in a try/catch and log or swallow any errors to prevent
unhandled promise rejections if the provided handler (e.g., handleError) throws;
and in SignUpEmailCodeCard.tsx (and SignUpPhoneCodeCard.tsx) add a concise code
comment next to staleTime: 100 explaining that this 100ms window is the intended
de-duplication/deduping window for “prepare fires once” semantics or add a unit
test asserting that preparing twice within 100ms only triggers a single prepare
call.

);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,15 @@ export const SignUpPhoneCodeCard = withCardStateProvider(() => {
// because the verification is already created on the Start screen
shouldAvoidPrepare || isAlternativePhoneCodeProvider
? undefined
: () =>
signUp
.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined })
.catch(err => handleError(err, [], card.setError)),
: () => signUp.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined }),
{
name: 'signUp.preparePhoneNumberVerification',
strategy: 'phone_code',
number: signUp.phoneNumber,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 46 to 48

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.

💡 Verification agent

🧩 Analysis chain

Verify onError behavior with handleError rethrows; confirm staleTime=100ms is intentional

handleError rethrows unknown errors. Ensure useFetch guards against exceptions thrown inside onError so we don’t crash render. Also, validate that staleTime: 100 sufficiently dedupes initial rerenders without suppressing legitimate retries.

Run:

Expected:

  • useFetch calls onError inside a try/catch or otherwise prevents rethrow from breaking React render.
  • staleTime choice is deliberate (doc or comment) to coalesce only immediate duplicate prepares.

🏁 Script executed:

#!/bin/bash# Inspect onError invocation and error propagation in useFetch
rg -nP -C3 'function\s+useFetch|\bonError\b|\bcatch\s*\(' packages/clerk-js/src/ui/hooks/useFetch.ts
# Scan all useFetch callers for very short staleTime and generic names that could collide
rg -nP -C1 'useFetch\(' packages | sed -n '1,200p'

Length of output: 5044


🏁 Script executed:

#!/bin/bash# Search for handleError definition and usage across clerk-js
rg -nR 'handleError' -n packages/clerk-js

Length of output: 446


🏁 Script executed:

#!/bin/bash# Show useFetch implementation including error invocation context
rg -nP -C10 'function\s+useFetch' packages/clerk-js/src/ui/hooks/useFetch.ts
rg -nP -C10 '\.catch' packages/clerk-js/src/ui/hooks/useFetch.ts
# Show import and usage of handleError in SignUpPhoneCodeCard
sed -n '1,50p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx
# Search for handleError definition across the repo
rg -n 'handleError' -n .

Length of output: 25854


🏁 Script executed:

#!/bin/bash# Inspect staleTime handling in useFetch
rg -nP -C5 'staleTime' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 1310


🏁 Script executed:

#!/bin/bash
rg -nP -C10 'export const handleError' packages/clerk-js/src/ui/utils/errorHandler.ts

Length of output: 644


Guard onError invocation and confirm staleTime override

  • Wrap options.onError(e) in a try/catch inside useFetch’s .catch handler (packages/clerk-js/src/ui/hooks/useFetch.ts ~L170); since handleError can rethrow unknown errors, unhandled promise rejections may occur.
  • The 100 ms staleTime override matches other prepare hooks (e.g. SignInFactorOneCodeForm) and is intentional to dedupe rapid remounts without impacting legitimate retries.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx around
lines 46-48, guard the onError invocation by wrapping options.onError(e) in a
try/catch inside the useFetch .catch handler so any error thrown by handleError
(or other callbacks) does not create an unhandled promise rejection; if the
callback throws, catch it and log or forward to card.setError as a fallback.
Leave the staleTime: 100 override as-is (it is intentional to dedupe rapid
remounts).

);

Expand Down
6 changes: 5 additions & 1 deletion packages/clerk-js/src/ui/hooks/useFetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ export const useFetch = <K, T>(
options?: {
throttleTime?: number;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
staleTime?: number;
},
resourceId?: string,
Expand DownExpand Up@@ -166,14 +167,17 @@ export const useFetch = <K, T>(
}, waitTime);
}
})
.catch((e: Error) => {
.catch((e: Error | null) => {
setCache({
data: getCache()?.data ?? null,
isLoading: false,
isValidating: false,
error: e,
cachedAt: Date.now(),
});
if (e) {
options?.onError?.(e);
}
});
}, [serialize(params), setCache, getCache, revalidateCache]);

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-days-tan.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Fixes issue where "prepare" API request would only fire once, preventing end users from receiving fresh otp codes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,19 +82,11 @@ export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) =>
.catch(err => handleError(err, [], card.setError));
};

useFetch(
shouldAvoidPrepare
? undefined
: () =>
signIn
?.prepareFirstFactor(props.factor)
.then(() => props.onFactorPrepare())
.catch(err => handleError(err, [], card.setError)),
cacheKey,
{
staleTime: 100,
},
);
useFetch(shouldAvoidPrepare ? undefined : () => signIn?.prepareFirstFactor(props.factor), cacheKey, {
staleTime: 100,
onSuccess: () => props.onFactorPrepare(),
onError: err => handleError(err, [], card.setError),
});

const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
signIn
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,9 +56,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand DownExpand Up@@ -91,9 +93,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123_whatsapp',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,19 +24,15 @@ export const SignUpEmailCodeCard = () => {

// TODO: Introduce a useMutation to handle mutating requests
useFetch(
shouldAvoidPrepare
? undefined
: () =>
signUp
.prepareEmailAddressVerification({ strategy: 'email_code' })
.catch(err => handleError(err, [], card.setError)),
shouldAvoidPrepare ? undefined : () => signUp.prepareEmailAddressVerification({ strategy: 'email_code' }),
{
name: 'prepare',
strategy: 'email_code',
number: signUp.emailAddress,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 34 to 36

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.

💡 Verification agent

🧩 Analysis chain

Verify onError safety with handleError rethrows; double-check staleTime

Same concern as the phone code card: confirm useFetch won’t surface unhandled exceptions if handleError rethrows, and that staleTime: 100 meets the intended dedupe window.

Run:

Expected:

  • onError invocation cannot crash the component tree.
  • Comment or test validates the 100ms window is sufficient for “prepare fires once” semantics.

🏁 Script executed:

#!/bin/bash# Confirm try/catch around user-provided onError and the internal error flow
rg -nP -C3 'onError\s*\(|try|catch|setState' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 285


🏁 Script executed:

#!/bin/bash# Inspect the beginning of the useFetch hook to locate onError invocation
sed -n '1,200p' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 5357


🏁 Script executed:

#!/bin/bash# Inspect the SignUpEmailCodeCard and SignUpPhoneCodeCard for staleTime and onError usage
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx
echo"---"
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx

Length of output: 5735


🏁 Script executed:

#!/bin/bash
rg -n "export (function|const) handleError" -n packages/clerk-js/src/ui/utils

Length of output: 209


Ensure onError callback safety and clarify staleTime usage

  • In useFetch (packages/clerk-js/src/ui/hooks/useFetch.ts), wrap the options.onError?.(e) invocation in a try/catch to prevent unhandled promise rejections if handleError throws.
  • Add a code comment or unit test to verify that the 100 ms staleTime (in SignUpEmailCodeCard.tsx and SignUpPhoneCodeCard.tsx) provides the intended dedupe window for “prepare fires once” semantics.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx around
lines 34-36, the options passed to useFetch set staleTime: 100 and onError: err
=> handleError(err, [], card.setError); update two things: in
packages/clerk-js/src/ui/hooks/useFetch.ts wrap the invocation of
options.onError?.(e) in a try/catch and log or swallow any errors to prevent
unhandled promise rejections if the provided handler (e.g., handleError) throws;
and in SignUpEmailCodeCard.tsx (and SignUpPhoneCodeCard.tsx) add a concise code
comment next to staleTime: 100 explaining that this 100ms window is the intended
de-duplication/deduping window for “prepare fires once” semantics or add a unit
test asserting that preparing twice within 100ms only triggers a single prepare
call.

);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,15 @@ export const SignUpPhoneCodeCard = withCardStateProvider(() => {
// because the verification is already created on the Start screen
shouldAvoidPrepare || isAlternativePhoneCodeProvider
? undefined
: () =>
signUp
.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined })
.catch(err => handleError(err, [], card.setError)),
: () => signUp.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined }),
{
name: 'signUp.preparePhoneNumberVerification',
strategy: 'phone_code',
number: signUp.phoneNumber,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 46 to 48

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.

💡 Verification agent

🧩 Analysis chain

Verify onError behavior with handleError rethrows; confirm staleTime=100ms is intentional

handleError rethrows unknown errors. Ensure useFetch guards against exceptions thrown inside onError so we don’t crash render. Also, validate that staleTime: 100 sufficiently dedupes initial rerenders without suppressing legitimate retries.

Run:

Expected:

  • useFetch calls onError inside a try/catch or otherwise prevents rethrow from breaking React render.
  • staleTime choice is deliberate (doc or comment) to coalesce only immediate duplicate prepares.

🏁 Script executed:

#!/bin/bash# Inspect onError invocation and error propagation in useFetch
rg -nP -C3 'function\s+useFetch|\bonError\b|\bcatch\s*\(' packages/clerk-js/src/ui/hooks/useFetch.ts
# Scan all useFetch callers for very short staleTime and generic names that could collide
rg -nP -C1 'useFetch\(' packages | sed -n '1,200p'

Length of output: 5044


🏁 Script executed:

#!/bin/bash# Search for handleError definition and usage across clerk-js
rg -nR 'handleError' -n packages/clerk-js

Length of output: 446


🏁 Script executed:

#!/bin/bash# Show useFetch implementation including error invocation context
rg -nP -C10 'function\s+useFetch' packages/clerk-js/src/ui/hooks/useFetch.ts
rg -nP -C10 '\.catch' packages/clerk-js/src/ui/hooks/useFetch.ts
# Show import and usage of handleError in SignUpPhoneCodeCard
sed -n '1,50p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx
# Search for handleError definition across the repo
rg -n 'handleError' -n .

Length of output: 25854


🏁 Script executed:

#!/bin/bash# Inspect staleTime handling in useFetch
rg -nP -C5 'staleTime' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 1310


🏁 Script executed:

#!/bin/bash
rg -nP -C10 'export const handleError' packages/clerk-js/src/ui/utils/errorHandler.ts

Length of output: 644


Guard onError invocation and confirm staleTime override

  • Wrap options.onError(e) in a try/catch inside useFetch’s .catch handler (packages/clerk-js/src/ui/hooks/useFetch.ts ~L170); since handleError can rethrow unknown errors, unhandled promise rejections may occur.
  • The 100 ms staleTime override matches other prepare hooks (e.g. SignInFactorOneCodeForm) and is intentional to dedupe rapid remounts without impacting legitimate retries.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx around
lines 46-48, guard the onError invocation by wrapping options.onError(e) in a
try/catch inside the useFetch .catch handler so any error thrown by handleError
(or other callbacks) does not create an unhandled promise rejection; if the
callback throws, catch it and log or forward to card.setError as a fallback.
Leave the staleTime: 100 override as-is (it is intentional to dedupe rapid
remounts).

);

Expand Down
6 changes: 5 additions & 1 deletion packages/clerk-js/src/ui/hooks/useFetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ export const useFetch = <K, T>(
options?: {
throttleTime?: number;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
staleTime?: number;
},
resourceId?: string,
Expand DownExpand Up@@ -166,14 +167,17 @@ export const useFetch = <K, T>(
}, waitTime);
}
})
.catch((e: Error) => {
.catch((e: Error | null) => {
setCache({
data: getCache()?.data ?? null,
isLoading: false,
isValidating: false,
error: e,
cachedAt: Date.now(),
});
if (e) {
options?.onError?.(e);
}
});
}, [serialize(params), setCache, getCache, revalidateCache]);

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-days-tan.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Fixes issue where "prepare" API request would only fire once, preventing end users from receiving fresh otp codes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,19 +82,11 @@ export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) =>
.catch(err => handleError(err, [], card.setError));
};

useFetch(
shouldAvoidPrepare
? undefined
: () =>
signIn
?.prepareFirstFactor(props.factor)
.then(() => props.onFactorPrepare())
.catch(err => handleError(err, [], card.setError)),
cacheKey,
{
staleTime: 100,
},
);
useFetch(shouldAvoidPrepare ? undefined : () => signIn?.prepareFirstFactor(props.factor), cacheKey, {
staleTime: 100,
onSuccess: () => props.onFactorPrepare(),
onError: err => handleError(err, [], card.setError),
});

const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
signIn
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,9 +56,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand DownExpand Up@@ -91,9 +93,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123_whatsapp',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,19 +24,15 @@ export const SignUpEmailCodeCard = () => {

// TODO: Introduce a useMutation to handle mutating requests
useFetch(
shouldAvoidPrepare
? undefined
: () =>
signUp
.prepareEmailAddressVerification({ strategy: 'email_code' })
.catch(err => handleError(err, [], card.setError)),
shouldAvoidPrepare ? undefined : () => signUp.prepareEmailAddressVerification({ strategy: 'email_code' }),
{
name: 'prepare',
strategy: 'email_code',
number: signUp.emailAddress,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 34 to 36

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.

💡 Verification agent

🧩 Analysis chain

Verify onError safety with handleError rethrows; double-check staleTime

Same concern as the phone code card: confirm useFetch won’t surface unhandled exceptions if handleError rethrows, and that staleTime: 100 meets the intended dedupe window.

Run:

Expected:

  • onError invocation cannot crash the component tree.
  • Comment or test validates the 100ms window is sufficient for “prepare fires once” semantics.

🏁 Script executed:

#!/bin/bash# Confirm try/catch around user-provided onError and the internal error flow
rg -nP -C3 'onError\s*\(|try|catch|setState' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 285


🏁 Script executed:

#!/bin/bash# Inspect the beginning of the useFetch hook to locate onError invocation
sed -n '1,200p' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 5357


🏁 Script executed:

#!/bin/bash# Inspect the SignUpEmailCodeCard and SignUpPhoneCodeCard for staleTime and onError usage
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx
echo"---"
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx

Length of output: 5735


🏁 Script executed:

#!/bin/bash
rg -n "export (function|const) handleError" -n packages/clerk-js/src/ui/utils

Length of output: 209


Ensure onError callback safety and clarify staleTime usage

  • In useFetch (packages/clerk-js/src/ui/hooks/useFetch.ts), wrap the options.onError?.(e) invocation in a try/catch to prevent unhandled promise rejections if handleError throws.
  • Add a code comment or unit test to verify that the 100 ms staleTime (in SignUpEmailCodeCard.tsx and SignUpPhoneCodeCard.tsx) provides the intended dedupe window for “prepare fires once” semantics.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx around
lines 34-36, the options passed to useFetch set staleTime: 100 and onError: err
=> handleError(err, [], card.setError); update two things: in
packages/clerk-js/src/ui/hooks/useFetch.ts wrap the invocation of
options.onError?.(e) in a try/catch and log or swallow any errors to prevent
unhandled promise rejections if the provided handler (e.g., handleError) throws;
and in SignUpEmailCodeCard.tsx (and SignUpPhoneCodeCard.tsx) add a concise code
comment next to staleTime: 100 explaining that this 100ms window is the intended
de-duplication/deduping window for “prepare fires once” semantics or add a unit
test asserting that preparing twice within 100ms only triggers a single prepare
call.

);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,15 @@ export const SignUpPhoneCodeCard = withCardStateProvider(() => {
// because the verification is already created on the Start screen
shouldAvoidPrepare || isAlternativePhoneCodeProvider
? undefined
: () =>
signUp
.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined })
.catch(err => handleError(err, [], card.setError)),
: () => signUp.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined }),
{
name: 'signUp.preparePhoneNumberVerification',
strategy: 'phone_code',
number: signUp.phoneNumber,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 46 to 48

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.

💡 Verification agent

🧩 Analysis chain

Verify onError behavior with handleError rethrows; confirm staleTime=100ms is intentional

handleError rethrows unknown errors. Ensure useFetch guards against exceptions thrown inside onError so we don’t crash render. Also, validate that staleTime: 100 sufficiently dedupes initial rerenders without suppressing legitimate retries.

Run:

Expected:

  • useFetch calls onError inside a try/catch or otherwise prevents rethrow from breaking React render.
  • staleTime choice is deliberate (doc or comment) to coalesce only immediate duplicate prepares.

🏁 Script executed:

#!/bin/bash# Inspect onError invocation and error propagation in useFetch
rg -nP -C3 'function\s+useFetch|\bonError\b|\bcatch\s*\(' packages/clerk-js/src/ui/hooks/useFetch.ts
# Scan all useFetch callers for very short staleTime and generic names that could collide
rg -nP -C1 'useFetch\(' packages | sed -n '1,200p'

Length of output: 5044


🏁 Script executed:

#!/bin/bash# Search for handleError definition and usage across clerk-js
rg -nR 'handleError' -n packages/clerk-js

Length of output: 446


🏁 Script executed:

#!/bin/bash# Show useFetch implementation including error invocation context
rg -nP -C10 'function\s+useFetch' packages/clerk-js/src/ui/hooks/useFetch.ts
rg -nP -C10 '\.catch' packages/clerk-js/src/ui/hooks/useFetch.ts
# Show import and usage of handleError in SignUpPhoneCodeCard
sed -n '1,50p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx
# Search for handleError definition across the repo
rg -n 'handleError' -n .

Length of output: 25854


🏁 Script executed:

#!/bin/bash# Inspect staleTime handling in useFetch
rg -nP -C5 'staleTime' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 1310


🏁 Script executed:

#!/bin/bash
rg -nP -C10 'export const handleError' packages/clerk-js/src/ui/utils/errorHandler.ts

Length of output: 644


Guard onError invocation and confirm staleTime override

  • Wrap options.onError(e) in a try/catch inside useFetch’s .catch handler (packages/clerk-js/src/ui/hooks/useFetch.ts ~L170); since handleError can rethrow unknown errors, unhandled promise rejections may occur.
  • The 100 ms staleTime override matches other prepare hooks (e.g. SignInFactorOneCodeForm) and is intentional to dedupe rapid remounts without impacting legitimate retries.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx around
lines 46-48, guard the onError invocation by wrapping options.onError(e) in a
try/catch inside the useFetch .catch handler so any error thrown by handleError
(or other callbacks) does not create an unhandled promise rejection; if the
callback throws, catch it and log or forward to card.setError as a fallback.
Leave the staleTime: 100 override as-is (it is intentional to dedupe rapid
remounts).

);

Expand Down
6 changes: 5 additions & 1 deletion packages/clerk-js/src/ui/hooks/useFetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ export const useFetch = <K, T>(
options?: {
throttleTime?: number;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
staleTime?: number;
},
resourceId?: string,
Expand DownExpand Up@@ -166,14 +167,17 @@ export const useFetch = <K, T>(
}, waitTime);
}
})
.catch((e: Error) => {
.catch((e: Error | null) => {
setCache({
data: getCache()?.data ?? null,
isLoading: false,
isValidating: false,
error: e,
cachedAt: Date.now(),
});
if (e) {
options?.onError?.(e);
}
});
}, [serialize(params), setCache, getCache, revalidateCache]);

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-days-tan.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Fixes issue where "prepare" API request would only fire once, preventing end users from receiving fresh otp codes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,19 +82,11 @@ export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) =>
.catch(err => handleError(err, [], card.setError));
};

useFetch(
shouldAvoidPrepare
? undefined
: () =>
signIn
?.prepareFirstFactor(props.factor)
.then(() => props.onFactorPrepare())
.catch(err => handleError(err, [], card.setError)),
cacheKey,
{
staleTime: 100,
},
);
useFetch(shouldAvoidPrepare ? undefined : () => signIn?.prepareFirstFactor(props.factor), cacheKey, {
staleTime: 100,
onSuccess: () => props.onFactorPrepare(),
onError: err => handleError(err, [], card.setError),
});

const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
signIn
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,9 +56,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand DownExpand Up@@ -91,9 +93,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123_whatsapp',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,19 +24,15 @@ export const SignUpEmailCodeCard = () => {

// TODO: Introduce a useMutation to handle mutating requests
useFetch(
shouldAvoidPrepare
? undefined
: () =>
signUp
.prepareEmailAddressVerification({ strategy: 'email_code' })
.catch(err => handleError(err, [], card.setError)),
shouldAvoidPrepare ? undefined : () => signUp.prepareEmailAddressVerification({ strategy: 'email_code' }),
{
name: 'prepare',
strategy: 'email_code',
number: signUp.emailAddress,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 34 to 36

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.

💡 Verification agent

🧩 Analysis chain

Verify onError safety with handleError rethrows; double-check staleTime

Same concern as the phone code card: confirm useFetch won’t surface unhandled exceptions if handleError rethrows, and that staleTime: 100 meets the intended dedupe window.

Run:

Expected:

  • onError invocation cannot crash the component tree.
  • Comment or test validates the 100ms window is sufficient for “prepare fires once” semantics.

🏁 Script executed:

#!/bin/bash# Confirm try/catch around user-provided onError and the internal error flow
rg -nP -C3 'onError\s*\(|try|catch|setState' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 285


🏁 Script executed:

#!/bin/bash# Inspect the beginning of the useFetch hook to locate onError invocation
sed -n '1,200p' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 5357


🏁 Script executed:

#!/bin/bash# Inspect the SignUpEmailCodeCard and SignUpPhoneCodeCard for staleTime and onError usage
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx
echo"---"
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx

Length of output: 5735


🏁 Script executed:

#!/bin/bash
rg -n "export (function|const) handleError" -n packages/clerk-js/src/ui/utils

Length of output: 209


Ensure onError callback safety and clarify staleTime usage

  • In useFetch (packages/clerk-js/src/ui/hooks/useFetch.ts), wrap the options.onError?.(e) invocation in a try/catch to prevent unhandled promise rejections if handleError throws.
  • Add a code comment or unit test to verify that the 100 ms staleTime (in SignUpEmailCodeCard.tsx and SignUpPhoneCodeCard.tsx) provides the intended dedupe window for “prepare fires once” semantics.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx around
lines 34-36, the options passed to useFetch set staleTime: 100 and onError: err
=> handleError(err, [], card.setError); update two things: in
packages/clerk-js/src/ui/hooks/useFetch.ts wrap the invocation of
options.onError?.(e) in a try/catch and log or swallow any errors to prevent
unhandled promise rejections if the provided handler (e.g., handleError) throws;
and in SignUpEmailCodeCard.tsx (and SignUpPhoneCodeCard.tsx) add a concise code
comment next to staleTime: 100 explaining that this 100ms window is the intended
de-duplication/deduping window for “prepare fires once” semantics or add a unit
test asserting that preparing twice within 100ms only triggers a single prepare
call.

);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,15 @@ export const SignUpPhoneCodeCard = withCardStateProvider(() => {
// because the verification is already created on the Start screen
shouldAvoidPrepare || isAlternativePhoneCodeProvider
? undefined
: () =>
signUp
.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined })
.catch(err => handleError(err, [], card.setError)),
: () => signUp.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined }),
{
name: 'signUp.preparePhoneNumberVerification',
strategy: 'phone_code',
number: signUp.phoneNumber,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 46 to 48

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.

💡 Verification agent

🧩 Analysis chain

Verify onError behavior with handleError rethrows; confirm staleTime=100ms is intentional

handleError rethrows unknown errors. Ensure useFetch guards against exceptions thrown inside onError so we don’t crash render. Also, validate that staleTime: 100 sufficiently dedupes initial rerenders without suppressing legitimate retries.

Run:

Expected:

  • useFetch calls onError inside a try/catch or otherwise prevents rethrow from breaking React render.
  • staleTime choice is deliberate (doc or comment) to coalesce only immediate duplicate prepares.

🏁 Script executed:

#!/bin/bash# Inspect onError invocation and error propagation in useFetch
rg -nP -C3 'function\s+useFetch|\bonError\b|\bcatch\s*\(' packages/clerk-js/src/ui/hooks/useFetch.ts
# Scan all useFetch callers for very short staleTime and generic names that could collide
rg -nP -C1 'useFetch\(' packages | sed -n '1,200p'

Length of output: 5044


🏁 Script executed:

#!/bin/bash# Search for handleError definition and usage across clerk-js
rg -nR 'handleError' -n packages/clerk-js

Length of output: 446


🏁 Script executed:

#!/bin/bash# Show useFetch implementation including error invocation context
rg -nP -C10 'function\s+useFetch' packages/clerk-js/src/ui/hooks/useFetch.ts
rg -nP -C10 '\.catch' packages/clerk-js/src/ui/hooks/useFetch.ts
# Show import and usage of handleError in SignUpPhoneCodeCard
sed -n '1,50p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx
# Search for handleError definition across the repo
rg -n 'handleError' -n .

Length of output: 25854


🏁 Script executed:

#!/bin/bash# Inspect staleTime handling in useFetch
rg -nP -C5 'staleTime' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 1310


🏁 Script executed:

#!/bin/bash
rg -nP -C10 'export const handleError' packages/clerk-js/src/ui/utils/errorHandler.ts

Length of output: 644


Guard onError invocation and confirm staleTime override

  • Wrap options.onError(e) in a try/catch inside useFetch’s .catch handler (packages/clerk-js/src/ui/hooks/useFetch.ts ~L170); since handleError can rethrow unknown errors, unhandled promise rejections may occur.
  • The 100 ms staleTime override matches other prepare hooks (e.g. SignInFactorOneCodeForm) and is intentional to dedupe rapid remounts without impacting legitimate retries.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx around
lines 46-48, guard the onError invocation by wrapping options.onError(e) in a
try/catch inside the useFetch .catch handler so any error thrown by handleError
(or other callbacks) does not create an unhandled promise rejection; if the
callback throws, catch it and log or forward to card.setError as a fallback.
Leave the staleTime: 100 override as-is (it is intentional to dedupe rapid
remounts).

);

Expand Down
6 changes: 5 additions & 1 deletion packages/clerk-js/src/ui/hooks/useFetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ export const useFetch = <K, T>(
options?: {
throttleTime?: number;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
staleTime?: number;
},
resourceId?: string,
Expand DownExpand Up@@ -166,14 +167,17 @@ export const useFetch = <K, T>(
}, waitTime);
}
})
.catch((e: Error) => {
.catch((e: Error | null) => {
setCache({
data: getCache()?.data ?? null,
isLoading: false,
isValidating: false,
error: e,
cachedAt: Date.now(),
});
if (e) {
options?.onError?.(e);
}
});
}, [serialize(params), setCache, getCache, revalidateCache]);

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-days-tan.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Fixes issue where "prepare" API request would only fire once, preventing end users from receiving fresh otp codes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,19 +82,11 @@ export const SignInFactorOneCodeForm = (props: SignInFactorOneCodeFormProps) =>
.catch(err => handleError(err, [], card.setError));
};

useFetch(
shouldAvoidPrepare
? undefined
: () =>
signIn
?.prepareFirstFactor(props.factor)
.then(() => props.onFactorPrepare())
.catch(err => handleError(err, [], card.setError)),
cacheKey,
{
staleTime: 100,
},
);
useFetch(shouldAvoidPrepare ? undefined : () => signIn?.prepareFirstFactor(props.factor), cacheKey, {
staleTime: 100,
onSuccess: () => props.onFactorPrepare(),
onError: err => handleError(err, [], card.setError),
});

const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => {
signIn
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,9 +56,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand DownExpand Up@@ -91,9 +93,11 @@ describe('SignInFactorOneCodeForm', () => {
name: 'signIn.prepareFirstFactor',
factorKey: 'phone_code_idn_123_whatsapp',
},
{
expect.objectContaining({
staleTime: 100,
},
onSuccess: expect.any(Function),
onError: expect.any(Function),
}),
);
});

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,19 +24,15 @@ export const SignUpEmailCodeCard = () => {

// TODO: Introduce a useMutation to handle mutating requests
useFetch(
shouldAvoidPrepare
? undefined
: () =>
signUp
.prepareEmailAddressVerification({ strategy: 'email_code' })
.catch(err => handleError(err, [], card.setError)),
shouldAvoidPrepare ? undefined : () => signUp.prepareEmailAddressVerification({ strategy: 'email_code' }),
{
name: 'prepare',
strategy: 'email_code',
number: signUp.emailAddress,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 34 to 36

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.

💡 Verification agent

🧩 Analysis chain

Verify onError safety with handleError rethrows; double-check staleTime

Same concern as the phone code card: confirm useFetch won’t surface unhandled exceptions if handleError rethrows, and that staleTime: 100 meets the intended dedupe window.

Run:

Expected:

  • onError invocation cannot crash the component tree.
  • Comment or test validates the 100ms window is sufficient for “prepare fires once” semantics.

🏁 Script executed:

#!/bin/bash# Confirm try/catch around user-provided onError and the internal error flow
rg -nP -C3 'onError\s*\(|try|catch|setState' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 285


🏁 Script executed:

#!/bin/bash# Inspect the beginning of the useFetch hook to locate onError invocation
sed -n '1,200p' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 5357


🏁 Script executed:

#!/bin/bash# Inspect the SignUpEmailCodeCard and SignUpPhoneCodeCard for staleTime and onError usage
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx
echo"---"
sed -n '1,200p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx

Length of output: 5735


🏁 Script executed:

#!/bin/bash
rg -n "export (function|const) handleError" -n packages/clerk-js/src/ui/utils

Length of output: 209


Ensure onError callback safety and clarify staleTime usage

  • In useFetch (packages/clerk-js/src/ui/hooks/useFetch.ts), wrap the options.onError?.(e) invocation in a try/catch to prevent unhandled promise rejections if handleError throws.
  • Add a code comment or unit test to verify that the 100 ms staleTime (in SignUpEmailCodeCard.tsx and SignUpPhoneCodeCard.tsx) provides the intended dedupe window for “prepare fires once” semantics.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpEmailCodeCard.tsx around
lines 34-36, the options passed to useFetch set staleTime: 100 and onError: err
=> handleError(err, [], card.setError); update two things: in
packages/clerk-js/src/ui/hooks/useFetch.ts wrap the invocation of
options.onError?.(e) in a try/catch and log or swallow any errors to prevent
unhandled promise rejections if the provided handler (e.g., handleError) throws;
and in SignUpEmailCodeCard.tsx (and SignUpPhoneCodeCard.tsx) add a concise code
comment next to staleTime: 100 explaining that this 100ms window is the intended
de-duplication/deduping window for “prepare fires once” semantics or add a unit
test asserting that preparing twice within 100ms only triggers a single prepare
call.

);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,15 @@ export const SignUpPhoneCodeCard = withCardStateProvider(() => {
// because the verification is already created on the Start screen
shouldAvoidPrepare || isAlternativePhoneCodeProvider
? undefined
: () =>
signUp
.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined })
.catch(err => handleError(err, [], card.setError)),
: () => signUp.preparePhoneNumberVerification({ strategy: 'phone_code', channel: undefined }),
{
name: 'signUp.preparePhoneNumberVerification',
strategy: 'phone_code',
number: signUp.phoneNumber,
},
{
staleTime: 100,
onError: err => handleError(err, [], card.setError),
},
Comment on lines 46 to 48

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.

💡 Verification agent

🧩 Analysis chain

Verify onError behavior with handleError rethrows; confirm staleTime=100ms is intentional

handleError rethrows unknown errors. Ensure useFetch guards against exceptions thrown inside onError so we don’t crash render. Also, validate that staleTime: 100 sufficiently dedupes initial rerenders without suppressing legitimate retries.

Run:

Expected:

  • useFetch calls onError inside a try/catch or otherwise prevents rethrow from breaking React render.
  • staleTime choice is deliberate (doc or comment) to coalesce only immediate duplicate prepares.

🏁 Script executed:

#!/bin/bash# Inspect onError invocation and error propagation in useFetch
rg -nP -C3 'function\s+useFetch|\bonError\b|\bcatch\s*\(' packages/clerk-js/src/ui/hooks/useFetch.ts
# Scan all useFetch callers for very short staleTime and generic names that could collide
rg -nP -C1 'useFetch\(' packages | sed -n '1,200p'

Length of output: 5044


🏁 Script executed:

#!/bin/bash# Search for handleError definition and usage across clerk-js
rg -nR 'handleError' -n packages/clerk-js

Length of output: 446


🏁 Script executed:

#!/bin/bash# Show useFetch implementation including error invocation context
rg -nP -C10 'function\s+useFetch' packages/clerk-js/src/ui/hooks/useFetch.ts
rg -nP -C10 '\.catch' packages/clerk-js/src/ui/hooks/useFetch.ts
# Show import and usage of handleError in SignUpPhoneCodeCard
sed -n '1,50p' packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx
# Search for handleError definition across the repo
rg -n 'handleError' -n .

Length of output: 25854


🏁 Script executed:

#!/bin/bash# Inspect staleTime handling in useFetch
rg -nP -C5 'staleTime' packages/clerk-js/src/ui/hooks/useFetch.ts

Length of output: 1310


🏁 Script executed:

#!/bin/bash
rg -nP -C10 'export const handleError' packages/clerk-js/src/ui/utils/errorHandler.ts

Length of output: 644


Guard onError invocation and confirm staleTime override

  • Wrap options.onError(e) in a try/catch inside useFetch’s .catch handler (packages/clerk-js/src/ui/hooks/useFetch.ts ~L170); since handleError can rethrow unknown errors, unhandled promise rejections may occur.
  • The 100 ms staleTime override matches other prepare hooks (e.g. SignInFactorOneCodeForm) and is intentional to dedupe rapid remounts without impacting legitimate retries.
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignUp/SignUpPhoneCodeCard.tsx around
lines 46-48, guard the onError invocation by wrapping options.onError(e) in a
try/catch inside the useFetch .catch handler so any error thrown by handleError
(or other callbacks) does not create an unhandled promise rejection; if the
callback throws, catch it and log or forward to card.setError as a fallback.
Leave the staleTime: 100 override as-is (it is intentional to dedupe rapid
remounts).

);

Expand Down
6 changes: 5 additions & 1 deletion packages/clerk-js/src/ui/hooks/useFetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ export const useFetch = <K, T>(
options?: {
throttleTime?: number;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
staleTime?: number;
},
resourceId?: string,
Expand DownExpand Up@@ -166,14 +167,17 @@ export const useFetch = <K, T>(
}, waitTime);
}
})
.catch((e: Error) => {
.catch((e: Error | null) => {
setCache({
data: getCache()?.data ?? null,
isLoading: false,
isValidating: false,
error: e,
cachedAt: Date.now(),
});
if (e) {
options?.onError?.(e);
}
});
}, [serialize(params), setCache, getCache, revalidateCache]);

Expand Down
Loading