diff --git a/.changeset/hip-spiders-punch.md b/.changeset/hip-spiders-punch.md
new file mode 100644
index 00000000000..82b9bb5775c
--- /dev/null
+++ b/.changeset/hip-spiders-punch.md
@@ -0,0 +1,5 @@
+---
+'@clerk/clerk-js': patch
+---
+
+Fixes username form field errors to display messages according to the respective code sent in the error response.
diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx
index e058ca32fdc..0db3dc269a8 100644
--- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx
@@ -1,7 +1,9 @@
-import { OAUTH_PROVIDERS } from '@clerk/types';
+import { ClerkAPIResponseError } from '@clerk/shared/error';
+import { OAUTH_PROVIDERS } from '@clerk/shared/oauth';
+import { waitFor } from '@testing-library/dom';
import React from 'react';
-import { render, screen } from '../../../../testUtils';
+import { render, runFakeTimers, screen } from '../../../../testUtils';
import { bindCreateFixtures } from '../../../utils/test/createFixtures';
import { SignUpContinue } from '../SignUpContinue';
@@ -106,6 +108,86 @@ describe('SignUpContinue', () => {
screen.getByText(`Continue with ${name}`);
});
+ it('renders error for invalid username length', async () => {
+ const { wrapper, fixtures } = await createFixtures(f => {
+ f.withEmailAddress({ required: true });
+ f.withUsername({ required: true });
+ f.startSignUpWithEmailAddress({
+ emailVerificationStatus: 'verified',
+ });
+ });
+
+ fixtures.signUp.update.mockRejectedValue(
+ new ClerkAPIResponseError('Error', {
+ data: [
+ {
+ code: 'form_username_invalid_length',
+ long_message: 'some server error',
+ message: 'some server error',
+ meta: { param_name: 'username' },
+ },
+ ],
+ status: 400,
+ }),
+ );
+
+ await runFakeTimers(async timers => {
+ const { userEvent } = render(, { wrapper });
+ expect(screen.queryByText(/username/i)).toBeInTheDocument();
+ await userEvent.type(screen.getByLabelText(/username/i), 'clerkUser');
+ timers.runOnlyPendingTimers();
+ const button = screen.getByText('Continue');
+ await userEvent.click(button);
+ timers.runOnlyPendingTimers();
+
+ await waitFor(() => expect(fixtures.signUp.update).toHaveBeenCalled());
+ timers.runOnlyPendingTimers();
+ await waitFor(() =>
+ expect(screen.queryByText(/^Your username must be between 4 and 40 characters long./i)).toBeInTheDocument(),
+ );
+ });
+ });
+
+ it('renders error for existing username', async () => {
+ const { wrapper, fixtures } = await createFixtures(f => {
+ f.withEmailAddress({ required: true });
+ f.withUsername({ required: true });
+ f.startSignUpWithEmailAddress({
+ emailVerificationStatus: 'verified',
+ });
+ });
+
+ fixtures.signUp.update.mockRejectedValue(
+ new ClerkAPIResponseError('Error', {
+ data: [
+ {
+ code: 'form_identifier_exists',
+ long_message: 'some server error',
+ message: 'some server error',
+ meta: { param_name: 'username' },
+ },
+ ],
+ status: 400,
+ }),
+ );
+
+ await runFakeTimers(async timers => {
+ const { userEvent } = render(, { wrapper });
+ expect(screen.queryByText(/username/i)).toBeInTheDocument();
+ await userEvent.type(screen.getByLabelText(/username/i), 'clerkUser');
+ timers.runOnlyPendingTimers();
+ const button = screen.getByText('Continue');
+ await userEvent.click(button);
+ timers.runOnlyPendingTimers();
+
+ await waitFor(() => expect(fixtures.signUp.update).toHaveBeenCalled());
+ timers.runOnlyPendingTimers();
+ await waitFor(() =>
+ expect(screen.queryByText(/^This username is taken. Please try another./i)).toBeInTheDocument(),
+ );
+ });
+ });
+
describe('Sign in Link', () => {
it('Shows the Sign In message with the appropriate link', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
diff --git a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts
index 4edbdaa623b..618589791b0 100644
--- a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts
+++ b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts
@@ -250,6 +250,7 @@ const createSignUpFixtureHelpers = (baseClient: ClientJSON) => {
status: emailVerificationStatus,
},
},
+ missing_fields: [],
} as SignUpJSON;
};
@@ -341,6 +342,11 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => {
mode: SIGN_UP_MODES.PUBLIC,
};
+ us.username_settings = {
+ min_length: 4,
+ max_length: 40,
+ };
+
const emptyAttribute = {
first_factors: [],
second_factors: [],
diff --git a/packages/clerk-js/src/ui/utils/usernameUtils.ts b/packages/clerk-js/src/ui/utils/usernameUtils.ts
index 8cf8732843e..b1989987939 100644
--- a/packages/clerk-js/src/ui/utils/usernameUtils.ts
+++ b/packages/clerk-js/src/ui/utils/usernameUtils.ts
@@ -8,19 +8,28 @@ type LocalizationConfigProps = {
usernameSettings: Pick;
};
-export const createUsernameError = (errors: ClerkAPIError[], localizationConfig: LocalizationConfigProps) => {
+const INVALID_LENGTH = 'form_username_invalid_length';
+
+export const createUsernameError = (
+ errors: ClerkAPIError[],
+ localizationConfig: LocalizationConfigProps,
+): ClerkAPIError | string | undefined => {
const { t, usernameSettings } = localizationConfig;
+ const clerkApiError = errors[0] as ClerkAPIError | undefined;
+
if (!localizationConfig) {
- return errors[0].longMessage;
+ return clerkApiError;
}
- const msg = t(
- localizationKeys('unstable__errors.form_username_invalid_length', {
- min_length: usernameSettings.min_length,
- max_length: usernameSettings.max_length,
- }),
- );
+ if (clerkApiError?.code === INVALID_LENGTH) {
+ return t(
+ localizationKeys(`unstable__errors.${INVALID_LENGTH}`, {
+ min_length: usernameSettings.min_length,
+ max_length: usernameSettings.max_length,
+ }),
+ );
+ }
- return msg;
+ return clerkApiError;
};