ChallengeView is the single OTP component used across all verification flows in ailita-library — email change confirmation, TOTP entry, login OTP, and password recovery all render it. It handles auto-submission, resend cooldown, attempt tracking, and the dual-response discriminated union from a single interface. You never need to build a digit input or countdown timer yourself — mount ChallengeView with a challengeId and the correct callback, and the rest is handled internally.
Props
The ID of the challenge to verify. Obtained from whatever API call created the challenge — for example,
LoginResponse.challengeId, EmailChangeResponse.challengeId, or ForgotPasswordResponse.challengeId. Pass this directly from the response that triggered the challenge flow.Called when a non-login challenge completes successfully. Receives the full
ChallengeResponse with status: 'COMPLETED'. Use this for SIGNUP, RECOVERY, FRICTION, and EMAIL_CHANGE challenge types. Do not use this for LOGIN_OTP challenges — see onLoginSuccess below.Called when a
LOGIN_OTP challenge completes and tokens have been set in the client. Receives the LoginResponse with a valid accessToken. Use this instead of onComplete when rendering ChallengeView for a login flow. By the time this callback fires, the access token and refresh token are already stored in memory — you can navigate directly to the authenticated area.Optional heading override. When omitted, defaults to
"Ingresa tu código 2FA" when type='TOTP', or "Verifica tu email" when type='EMAIL'.Optional subtitle override. When omitted, defaults to authenticator-app instructions for TOTP, or “enter the 6-digit code sent to your email” for EMAIL.
Controls UI mode. Default:
'EMAIL'. When 'TOTP': the resend/regenerate button is hidden — TOTP codes rotate on a time-based schedule and cannot be re-requested. When 'EMAIL': a countdown timer and “Resend code” button appear once the cooldown expires and canRegenerate is true.Dual-response contract
ChallengeView handles two distinct challenge categories, and each calls a different callback. Understanding which to implement is critical — using the wrong callback for a given challenge type will result in a broken integration.
| Challenge type | type prop | Callback to implement | Response type |
|---|---|---|---|
LOGIN_OTP | 'EMAIL' | onLoginSuccess | LoginResponse |
SIGNUP | 'EMAIL' | onComplete | ChallengeResponse |
RECOVERY | 'EMAIL' | onComplete | ChallengeResponse |
FRICTION | 'EMAIL' or 'TOTP' | onComplete | ChallengeResponse |
EMAIL_CHANGE | 'EMAIL' | onComplete | ChallengeResponse |
ChallengeView calls both callbacks through the complete() function in useChallenge. The hook resolves completeChallenge(challengeId, value) from the API, then checks 'accessToken' in data to determine which callback to invoke. You never interact with the type guard directly — just implement the right callback for your use case.Expiry and resend handling
Challenges have a TTL (expiresAt from ChallengeResponse). ChallengeView surfaces two expiry-related behaviors depending on the challenge type.
Resend countdown (EMAIL type only)
After the challenge is created, a cooldown period prevents immediate resend (canRegenerateAt from ChallengeResponse). ChallengeView tracks this with a countdown timer sourced from useChallenge.secondsUntilRegenerate. The “Resend code” button is hidden during the countdown and appears only when the timer hits zero and canRegenerate is true.
Attempt limit
The component displays “Intento N de M” whenattempts > 0. When attempts reaches maxAttempts, the backend marks the challenge as FAILED and subsequent submit calls will error. The component does not auto-redirect — your onComplete or onLoginSuccess handler is responsible for handling the FAILED state and offering the user a way to restart the flow.
TOTP (no regenerate)
Whentype='TOTP', the resend/regenerate section is entirely hidden. TOTP codes rotate on a time-based schedule — regenerating does not apply and will error if called. If the user’s authenticator app is generating wrong codes, the issue is clock drift: direct the user to sync their device clock in system settings. Do not render ChallengeView with type='EMAIL' for a TOTP challenge — the “Resend code” button will appear and fail on click since regenerate is not valid for authenticator-app challenges.
Usage examples
Login flow (LOGIN_OTP)
Email change confirmation (non-login)
2FA verification (TOTP)
ChallengeView auto-submits when all 6 digits are filled — the user does not need to press the Verify button. The button remains visible as a fallback for cases where focus is lost or auto-submit does not trigger. Paste support is built in: pasting a 6-digit string into any digit input fills all slots and triggers auto-submit automatically.ChallengeResponse type
LoginResponse type
ForLOGIN_OTP challenges, the onLoginSuccess callback receives a LoginResponse. By the time the callback fires, accessToken and refreshToken have already been stored in the client — the response is passed to your callback for inspection only (e.g., if you need to redirect or update local state).
Error handling
When a user submits an incorrect code,useChallenge catches the error, increments attempts, and displays the backend error message inline. ChallengeView resets the digit inputs automatically so the user can retry without clearing manually.
Your onComplete and onLoginSuccess callbacks are only invoked on success — you do not need to handle errors inside them. If you need to respond to exhausted attempts or an expired challenge, check result.status in onComplete:
LOGIN_OTP challenges, a FAILED status means the login attempt is rejected. Redirect the user back to LoginForm to start a new login session.
Next steps
Auth Flows
Login state machine and ChallengeView integration from the LoginForm perspective
Profile Module
EmailChangeForm and TOTPSetup — components that use ChallengeView internally

