Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dfa3a97
chore: update @clerk/clerk-expo and @clerk/types to latest versions
Jan 19, 2026
3caea27
Merge pull request #45 from RonasIT/PRD-2163-update-clerk-version
veliseev93 Jan 19, 2026
b2b1332
feat: add separated auth hooks and simplify types
Jan 20, 2026
88a554d
fix: remove username as auth method
Jan 20, 2026
c18d870
feat: add JSDoc to updated sign-up and sign-in hooks
Jan 21, 2026
b9cdcfc
fix: revert unintentional changes
Jan 21, 2026
9a22787
feat: improve startSignUp params with clerk natives
Jan 22, 2026
73a89e2
chore: remove unused types
Jan 22, 2026
cb5deba
fix: minor fix
Jan 22, 2026
ac95d94
chore: fix clerk examples
Jan 22, 2026
18e8650
Merge pull request #46 from RonasIT/PRD-2164-improve-sign-up-sign-in-…
veliseev93 Jan 22, 2026
de4efe3
feat: enhance authorization flow and loading state management in useA…
Jan 22, 2026
9f6fc76
feat: add useUpdatePassword hook and update types for password manage…
Jan 22, 2026
569ba62
Merge pull request #47 from RonasIT/PRD-2165-clerk-fix-loading-state-…
veliseev93 Jan 26, 2026
b0dbcd2
feat: add use-update-identifier hook and enhance use-add-identifier w…
Jan 26, 2026
cd3d1f9
fix: force reload user model after adding new identifier
Jan 26, 2026
3eaf60d
Merge pull request #48 from RonasIT/PRD-2166-clerk-update-password-hook
veliseev93 Jan 27, 2026
cc41536
Merge branch 'PRD-2150-update-clerk-module' into PRD-2167-update-iden…
Jan 28, 2026
3ce12db
feat: enhance useAddIdentifier and useUpdateIdentifier hooks for bett…
Jan 28, 2026
04d46a1
docs: update documentation for use-update-identifier hook to clarify …
Jan 29, 2026
9229679
Merge pull request #49 from RonasIT/PRD-2167-update-identificator-hook
veliseev93 Feb 2, 2026
0922621
feat: enhance useResetPassword with verification functionality and im…
Feb 2, 2026
d50bd68
fix: minor fix
Feb 2, 2026
14cb5c2
feat: extend useResetPassword to include tokenTemplate parameter and …
Feb 2, 2026
a7aff08
Merge pull request #51 from RonasIT/PRD-2186-update-reset-password-hook
veliseev93 Feb 2, 2026
d022c2c
fix: simplify useResetPassword by removing unnecessary code factor ch…
Feb 11, 2026
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
1,742 changes: 1,492 additions & 250 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
"tslib": "^2.8.1"
},
"devDependencies": {
"@clerk/clerk-expo": "^2.19.6",
"@clerk/types": "^4.101.3",
"@clerk/clerk-expo": "^2.19.18",
"@clerk/types": "^4.101.11",
"@eslint/compat": "^1.4.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.37.0",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/src/features/clerk/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export * from './use-otp-verification';
export * from './use-auth-with-identifier';
export * from './use-add-identifier';
export * from './use-reset-password';
export * from './use-update-password';
export * from './use-update-identifier';
53 changes: 23 additions & 30 deletions src/lib/src/features/clerk/hooks/use-add-identifier.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
import { isClerkAPIResponseError, useUser } from '@clerk/clerk-expo';
import { EmailAddressResource, PhoneNumberResource } from '@clerk/types';
import { useState } from 'react';
import { ClerkApiError } from '../enums';
import { UseAddIdentifierReturn } from '../types';
import { IdentifierType, UseAddIdentifierReturn } from '../types';

/**
* Hook that provides functionality to add new email or phone number identifiers to a user's account and verify them using verification codes.
*
* @param {IdentifierType} type - Specifies the type of identifier (e.g., 'phone', 'email')
*
* @returns {UseAddIdentifierReturn} Object containing:
* - `createIdentifier` - A function to add a new email or phone number identifier to the user's account and prepare it for verification
* - `verifyCode` - A function to verify a code sent to the identifier, completing the verification process
* - `isCreating` - A boolean indicating whether an identifier is currently being added
* - `isVerifying` - A boolean indicating whether a verification code is currently being processed
*/
export function useAddIdentifier(): UseAddIdentifierReturn {
export function useAddIdentifier(type: IdentifierType): UseAddIdentifierReturn {
const { user } = useUser();
const [identifierResource, setIdentifierResource] = useState<PhoneNumberResource | EmailAddressResource>();
const [isCreating, setIsCreating] = useState(false);
const [isVerifying, setIsVerifying] = useState(false);

const isEmail = type === 'email';

const createIdentifier: UseAddIdentifierReturn['createIdentifier'] = async ({ identifier }) => {
setIsCreating(true);
const isEmail = identifier?.includes('@');

try {
isEmail
? await user?.createEmailAddress({ email: identifier })
: await user?.createPhoneNumber({ phoneNumber: identifier });

await user?.reload();
let resource = isEmail
? user?.emailAddresses.find((a) => a.emailAddress === identifier)
: user?.phoneNumbers.find((a) => a.phoneNumber === identifier);

// If the resource already exists, re-creating it will cause an error,
// so skip the creation step and go to the send verification code flow.
if (!resource) {
resource = isEmail
? await user?.createEmailAddress({ email: identifier })
: await user?.createPhoneNumber({ phoneNumber: identifier });

await user?.reload();
}
await prepareVerification({ isEmail, identifier });

await prepareVerification({ identifier, isEmail });
setIdentifierResource(resource);

return { isSuccess: true, user };
} catch (e) {
if (isClerkAPIResponseError(e)) {
const error = e.errors[0];

if (error?.code === ClerkApiError.FORM_IDENTIFIER_EXIST && !getIdentifierVerified({ identifier, isEmail })) {
await prepareVerification({ identifier, isEmail });

await user?.reload();

return { isSuccess: true, user };
} else {
return { error: e, user };
}
return { error: e, user };
}

return { user, isSuccess: false };
return { isSuccess: false, user };
} finally {
setIsCreating(false);
}
Expand Down Expand Up @@ -82,13 +83,5 @@ export function useAddIdentifier(): UseAddIdentifierReturn {
setIdentifierResource(isEmail ? emailResource : phoneResource);
};

const getIdentifierVerified = ({ identifier, isEmail }: { identifier: string; isEmail: boolean }): boolean => {
const identifierResource = isEmail
? user?.emailAddresses?.find((a) => a.emailAddress === identifier)
: user?.phoneNumbers?.find((a) => a.phoneNumber === identifier);

return identifierResource?.verification?.status === 'verified';
};

return { createIdentifier, verifyCode, isCreating, isVerifying };
}
35 changes: 24 additions & 11 deletions src/lib/src/features/clerk/hooks/use-auth-with-identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IdentifierMethodFor,
StartAuthParams,
StartSignInWithIdentifierReturn,
StartSignUpParams,
StartSignUpWithIdentifierReturn,
UseAuthWithIdentifierReturn,
} from '../types';
Expand Down Expand Up @@ -37,6 +38,8 @@ export function useAuthWithIdentifier<
const { signUp, signIn, setActive } = useClerkResources();
const { sendOtpCode, verifyCode: verifyOtpCode, isVerifying } = useOtpVerification();
const { getSessionToken } = useGetSessionToken();

const [isAuthorizing, setIsAuthorizing] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const strategy = method === 'emailAddress' ? 'email_code' : 'phone_code';

Expand Down Expand Up @@ -77,6 +80,7 @@ export function useAuthWithIdentifier<
): Promise<StartSignInWithIdentifierReturn<TVerifyBy> | StartSignUpWithIdentifierReturn<TMethod>> => {
const { identifier, password, tokenTemplate } = params;
const authMethod = isSignUp ? signUp : signIn;

const authAttempt = await authMethod?.create({ username: identifier, password });

if (authAttempt?.status === 'complete' && 'createdSessionId' in authAttempt) {
Expand Down Expand Up @@ -128,8 +132,8 @@ export function useAuthWithIdentifier<
setIsLoading(true);

return method === 'username'
? handleUsernameAuth(params as StartAuthParams<'password'>, true)
: handleEmailPhoneAuth(params, true);
? await handleUsernameAuth(params as StartAuthParams<'password'>, true)
: await handleEmailPhoneAuth(params, true);
} catch (error) {
return { error, signUp, isSuccess: false } as StartSignUpWithIdentifierReturn<TMethod>;
} finally {
Expand All @@ -142,8 +146,8 @@ export function useAuthWithIdentifier<
setIsLoading(true);

return method === 'username'
? handleUsernameAuth(params as StartAuthParams<'password'>, false)
: handleEmailPhoneAuth(params, false);
? await handleUsernameAuth(params as StartAuthParams<'password'>, false)
: await handleEmailPhoneAuth(params, false);
} catch (error) {
return { error, signIn, isSuccess: false } as StartSignInWithIdentifierReturn<TVerifyBy>;
} finally {
Expand All @@ -153,42 +157,51 @@ export function useAuthWithIdentifier<

const startAuthorization: UseAuthWithIdentifierReturn<TVerifyBy, TMethod>['startAuthorization'] = async (params) => {
try {
setIsLoading(true);
const result = await startSignUp(params);
setIsAuthorizing(true);

const result = await startSignUp(params as StartSignUpParams<TVerifyBy>);

if (result?.error && isClerkAPIResponseError(result.error)) {
const error = result.error.errors[0];

if (error?.code === ClerkApiError.FORM_IDENTIFIER_EXIST) {
return await startSignIn(params);
const signInResult = await startSignIn(params);

return { ...signInResult, isSignUp: false };
}
}

return { ...result, isSignUp: true };
} catch (error) {
return { error, signIn, signUp, isSuccess: false } as StartSignInWithIdentifierReturn<TVerifyBy>;
} finally {
setIsLoading(false);
setIsAuthorizing(false);
}
};

const verifyCode = async ({ code, tokenTemplate }: { code: string; tokenTemplate?: string }) => {
return verifyOtpCode({ code, strategy, tokenTemplate });
setIsLoading(true);

try {
return await verifyOtpCode({ code, strategy, tokenTemplate });
} finally {
setIsLoading(false);
}
};

if (method === 'username') {
return {
startSignIn,
startSignUp,
startAuthorization,
isLoading,
isLoading: isAuthorizing || isLoading,
} as UseAuthWithIdentifierReturn<TVerifyBy, TMethod>;
} else {
return {
startSignIn,
startSignUp,
startAuthorization,
isLoading,
isLoading: isAuthorizing || isLoading,
verifyCode,
isVerifying,
} as UseAuthWithIdentifierReturn<TVerifyBy, TMethod>;
Expand Down
5 changes: 3 additions & 2 deletions src/lib/src/features/clerk/hooks/use-otp-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function useOtpVerification(): UseOtpVerificationReturn {
};

const sendOtpCode: UseOtpVerificationReturn['sendOtpCode'] = async (strategy) => {
const isSignIn = !!signIn?.id;
const isSignIn = !!signIn?.id && !!signIn.identifier;

if (isSignIn) {
await sendSignInOtpCode(strategy);
Expand All @@ -55,7 +55,8 @@ export function useOtpVerification(): UseOtpVerificationReturn {
const verifyCode: UseOtpVerificationReturn['verifyCode'] = async ({ code, strategy, tokenTemplate }) => {
try {
setIsVerifying(true);
const isSignIn = !!signIn?.id;

const isSignIn = !!signIn?.id && !!signIn.identifier;

if (isSignIn) {
const completeSignIn = await signIn.attemptFirstFactor({
Expand Down
64 changes: 50 additions & 14 deletions src/lib/src/features/clerk/hooks/use-reset-password.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@


import { useState } from 'react';
import { OtpMethod, UseResetPasswordReturn } from '../types';
import { useClerkResources } from './use-clerk-resources';
Expand All @@ -11,17 +13,22 @@ import { useGetSessionToken } from './use-get-session-token';
*
* @returns {UseResetPasswordReturn} Object containing:
* - `startResetPassword` - A function to initiate the password reset process by sending a verification code to the user's email or phone number
* - `resetPassword` - A function to reset the user's password by verifying the code and setting a new password
* - `resetPassword` - A function to reset the user's password and setting a new password
* - `verifyCode` - A function to verify a code sent to the identifier, completing the verification process
* - `isCodeSending` - A boolean indicating if the verification code is being sent
* - `isResetting` - A boolean indicating if the password is being reset
* - `isVerifying` - A boolean indicating whether a verification code is currently being processed
*/
export function useResetPassword({ method }: { method: OtpMethod }): UseResetPasswordReturn {
const strategy = method === 'emailAddress' ? 'reset_password_email_code' : 'reset_password_phone_code';
const { signIn, setActive } = useClerkResources();
const { getSessionToken } = useGetSessionToken();

const [isCodeSending, setIsCodeSending] = useState(false);
const [isVerifying, setIsVerifying] = useState(false);
const [isResetting, setIsResetting] = useState(false);

const strategy = method === 'emailAddress' ? 'reset_password_email_code' : 'reset_password_phone_code';

const startResetPassword: UseResetPasswordReturn['startResetPassword'] = async ({ identifier }) => {
setIsCodeSending(true);

Expand All @@ -39,32 +46,61 @@ export function useResetPassword({ method }: { method: OtpMethod }): UseResetPas
}
};

const resetPassword: UseResetPasswordReturn['resetPassword'] = async ({ code, password, tokenTemplate }) => {
setIsResetting(true);
const verifyCode: UseResetPasswordReturn['verifyCode'] = async ({ code }) => {
setIsVerifying(true);

try {
const result = await signIn?.attemptFirstFactor({
await signIn?.attemptFirstFactor({
strategy,
code,
});

return { isSuccess: true, signIn };
} catch (error) {
return { isSuccess: false, signIn, error };
} finally {
setIsVerifying(false);
}
};

const resetPassword: UseResetPasswordReturn['resetPassword'] = async ({ password, tokenTemplate }) => {
setIsResetting(true);

try {
const result = await signIn?.resetPassword({
password,
});

if (result?.status === 'complete') {
setActive({ session: result.createdSessionId });
const { sessionToken } = await getSessionToken({ tokenTemplate });
await setActive({ session: result?.createdSessionId });

const { sessionToken, error } = await getSessionToken({ tokenTemplate });

if (sessionToken) {
return { isSuccess: true, signIn, sessionToken };
}
if (sessionToken) {
return {
isSuccess: true,
signIn,
sessionToken: sessionToken,
};
} else {
return {
signIn,
error,
isSuccess: false,
};
}
} catch (error) {
return { isSuccess: false, signIn, error };
} finally {
setIsResetting(false);
}

return { isSuccess: false, signIn };
};

return { startResetPassword, resetPassword, isCodeSending, isResetting };
return {
startResetPassword,
verifyCode,
resetPassword,
isCodeSending,
isVerifying,
isResetting,
};
}
Loading
Loading