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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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
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 number Diff line number Diff 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
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 number Diff line number Diff 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 Down Expand 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