Skip to content

Commit d93b0ed

Browse files
authored
feat(clerk-js,clerk-react,types): Signal errors (#6495)
1 parent 1517932 commit d93b0ed

File tree

7 files changed

+121
-12
lines changed

7 files changed

+121
-12
lines changed

.changeset/happy-dodos-sneeze.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/clerk-react': minor
4+
'@clerk/types': minor
5+
---
6+
7+
[Experimental] Signal Errors

packages/clerk-js/bundlewatch.config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"files": [
3-
{ "path": "./dist/clerk.js", "maxSize": "621KB" },
3+
{ "path": "./dist/clerk.js", "maxSize": "622KB" },
44
{ "path": "./dist/clerk.browser.js", "maxSize": "75KB" },
55
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "117KB" },
6-
{ "path": "./dist/clerk.headless*.js", "maxSize": "57.1KB" },
6+
{ "path": "./dist/clerk.headless*.js", "maxSize": "58KB" },
77
{ "path": "./dist/ui-common*.js", "maxSize": "113KB" },
88
{ "path": "./dist/ui-common*.legacy.*.js", "maxSize": "118KB" },
99
{ "path": "./dist/vendors*.js", "maxSize": "40.2KB" },

packages/clerk-js/src/core/resources/SignIn.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ class SignInFuture implements SignInFutureResource {
517517
});
518518

519519
return { error: null };
520-
} catch (err) {
520+
} catch (err: unknown) {
521521
eventBus.emit('resource:error', { resource: this.resource, error: err });
522522
return { error: err };
523523
}
@@ -530,7 +530,7 @@ class SignInFuture implements SignInFutureResource {
530530
path: this.resource.pathRoot,
531531
body: { identifier, password },
532532
});
533-
} catch (err) {
533+
} catch (err: unknown) {
534534
eventBus.emit('resource:error', { resource: this.resource, error: err });
535535
return { error: err };
536536
}

packages/clerk-js/src/core/signals.ts

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,96 @@
1+
import { isClerkAPIResponseError } from '@clerk/shared/error';
2+
import type { Errors } from '@clerk/types';
13
import { computed, signal } from 'alien-signals';
24

35
import type { SignIn } from './resources/SignIn';
46

57
export const signInSignal = signal<{ resource: SignIn | null }>({ resource: null });
6-
export const signInErrorSignal = signal<{ errors: unknown }>({ errors: null });
8+
export const signInErrorSignal = signal<{ error: unknown }>({ error: null });
79

810
export const signInComputedSignal = computed(() => {
911
const signIn = signInSignal().resource;
10-
const errors = signInErrorSignal().errors;
12+
const error = signInErrorSignal().error;
13+
14+
const errors = errorsToParsedErrors(error);
1115

1216
if (!signIn) {
13-
return { errors: null, signIn: null };
17+
return { errors, signIn: null };
1418
}
1519

1620
return { errors, signIn: signIn.__internal_future };
1721
});
22+
23+
/**
24+
* Converts an error to a parsed errors object that reports the specific fields that the error pertains to. Will put
25+
* generic non-API errors into the global array.
26+
*/
27+
function errorsToParsedErrors(error: unknown): Errors {
28+
const parsedErrors: Errors = {
29+
fields: {
30+
firstName: null,
31+
lastName: null,
32+
emailAddress: null,
33+
identifier: null,
34+
phoneNumber: null,
35+
password: null,
36+
username: null,
37+
code: null,
38+
captcha: null,
39+
legalAccepted: null,
40+
},
41+
raw: [],
42+
global: [],
43+
};
44+
45+
if (!isClerkAPIResponseError(error)) {
46+
parsedErrors.raw.push(error);
47+
parsedErrors.global.push(error);
48+
return parsedErrors;
49+
}
50+
51+
parsedErrors.raw.push(...error.errors);
52+
53+
error.errors.forEach(error => {
54+
if ('meta' in error && error.meta && 'paramName' in error.meta) {
55+
switch (error.meta.paramName) {
56+
case 'first_name':
57+
parsedErrors.fields.firstName = error;
58+
break;
59+
case 'last_name':
60+
parsedErrors.fields.lastName = error;
61+
break;
62+
case 'email_address':
63+
parsedErrors.fields.emailAddress = error;
64+
break;
65+
case 'identifier':
66+
parsedErrors.fields.identifier = error;
67+
break;
68+
case 'phone_number':
69+
parsedErrors.fields.phoneNumber = error;
70+
break;
71+
case 'password':
72+
parsedErrors.fields.password = error;
73+
break;
74+
case 'username':
75+
parsedErrors.fields.username = error;
76+
break;
77+
case 'code':
78+
parsedErrors.fields.code = error;
79+
break;
80+
case 'captcha':
81+
parsedErrors.fields.captcha = error;
82+
break;
83+
case 'legal_accepted':
84+
parsedErrors.fields.legalAccepted = error;
85+
break;
86+
default:
87+
parsedErrors.global.push(error);
88+
break;
89+
}
90+
} else {
91+
parsedErrors.global.push(error);
92+
}
93+
});
94+
95+
return parsedErrors;
96+
}

packages/clerk-js/src/core/state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class State implements StateInterface {
2121

2222
private onResourceError = (payload: { resource: BaseResource; error: unknown }) => {
2323
if (payload.resource instanceof SignIn) {
24-
this.signInErrorSignal({ errors: payload.error });
24+
this.signInErrorSignal({ error: payload.error });
2525
}
2626
};
2727

packages/react/src/hooks/useClerkSignal.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import type { SignInFutureResource } from '@clerk/types';
21
import { useCallback, useSyncExternalStore } from 'react';
32

43
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
54
import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvider';
65

7-
function useClerkSignal(signal: 'signIn'): { errors: unknown; signIn: SignInFutureResource | null } | null {
6+
function useClerkSignal(signal: 'signIn') {
87
useAssertWrappedByClerkProvider('useSignInSignal');
98

109
const clerk = useIsomorphicClerkContext();

packages/types/src/state.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
import type { SignInFutureResource } from './signIn';
22

3+
interface FieldError {
4+
code: string;
5+
longMessage?: string;
6+
message: string;
7+
}
8+
9+
interface FieldErrors {
10+
firstName: FieldError | null;
11+
lastName: FieldError | null;
12+
emailAddress: FieldError | null;
13+
identifier: FieldError | null;
14+
phoneNumber: FieldError | null;
15+
password: FieldError | null;
16+
username: FieldError | null;
17+
code: FieldError | null;
18+
captcha: FieldError | null;
19+
legalAccepted: FieldError | null;
20+
}
21+
22+
export interface Errors {
23+
fields: FieldErrors;
24+
raw: unknown[];
25+
global: unknown[]; // does not include any errors that could be parsed as a field error
26+
}
27+
328
export interface State {
429
/**
530
* A Signal that updates when the underlying `SignIn` resource changes, including errors.
631
*/
732
signInSignal: {
833
(): {
9-
errors: unknown;
34+
errors: Errors;
1035
signIn: SignInFutureResource | null;
1136
};
12-
(value: { errors: unknown; signIn: SignInFutureResource | null }): void;
1337
};
1438

1539
/**

0 commit comments

Comments
 (0)