Skip to content

Commit e72ab2b

Browse files
tmilewskibrkalowLekoArts
authored
feat(elements): Complete and Reset Updates (clerk#3343)
* chore(elements): Add changeset * Apply suggestions from code review Co-authored-by: Bryce Kalow <bryce@clerk.dev> * feat(elements): Add `complete` step to Sign Up and Sign In. [SDK-1712] * chore(elements): Remove comment * chore(elements): Remove complete step * chore(elements): Update sign-in Step docs * fix(elements): Fix follow-on initialization discrepancies (clerk#3377) * fix(elements): Follow-on machine initialization [SDK-1746] * chore(elements): Add changeset * chore(elements): Remove unnecessary TS comment * chore(elements): Add line item to changeset * chore(repo): Update changeset --------- Co-authored-by: Bryce Kalow <bryce@clerk.dev> Co-authored-by: LekoArts <lekoarts@gmail.com>
1 parent b27ca83 commit e72ab2b

File tree

11 files changed

+68
-32
lines changed

11 files changed

+68
-32
lines changed

.changeset/odd-coins-carry.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@clerk/elements': patch
3+
---
4+
5+
This release includes various smaller fixes and one dependency update:
6+
7+
- `xstate` was updated from `5.12.0` to `5.13.0`
8+
- Previously, the contents of the `fallback` prop were sometimes shown even if the user wasn't on the `start` step. This bug is fixed now.
9+
- Upon completion of an sign-in/sign-up attempt, don't immediately return to the `start` step. This fixes the issue of a "flash of content" that could e.g. be seen during sign-in with OAuth providers.
10+
- Some underlying fixes in Clerk Elements' XState logic were applied to make sure that during a sign-in/sign-up attempt the state is properly maintained. For example, if you visit an already completed attempt (some step of that flow) it now properly keeps track of that state.

package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/elements/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"@statelyai/inspect": "^0.3.0",
7575
"@xstate/react": "^4.1.1",
7676
"client-only": "^0.0.1",
77-
"xstate": "^5.12.0"
77+
"xstate": "^5.13.0"
7878
},
7979
"devDependencies": {
8080
"@clerk/clerk-react": "5.0.4",

packages/elements/src/internals/machines/sign-in/router.machine.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/error
1313
import { ThirdPartyMachine, ThirdPartyMachineId } from '~/internals/machines/third-party';
1414
import { shouldUseVirtualRouting } from '~/internals/machines/utils/next';
1515

16+
import { FormMachine } from '../form';
1617
import { SignInResetPasswordMachine } from './reset-password.machine';
1718
import type {
1819
SignInRouterContext,
@@ -41,6 +42,7 @@ export const SignInRouterMachineId = 'SignInRouter';
4142
export const SignInRouterMachine = setup({
4243
actors: {
4344
firstFactorMachine: SignInFirstFactorMachine,
45+
formMachine: FormMachine,
4446
resetPasswordMachine: SignInResetPasswordMachine,
4547
startMachine: SignInStartMachine,
4648
secondFactorMachine: SignInSecondFactorMachine,
@@ -60,8 +62,8 @@ export const SignInRouterMachine = setup({
6062
},
6163
navigateExternal: ({ context }, { path }: { path: string }) => context.router?.push(path),
6264
raiseNext: raise({ type: 'NEXT' }),
63-
setActive({ context, event }) {
64-
if (context.exampleMode) return;
65+
setActive: enqueueActions(({ enqueue, check, context, event }) => {
66+
if (check('isExampleMode')) return;
6567

6668
const lastActiveSessionId = context.clerk.client.lastActiveSessionId;
6769
const createdSessionId = ((event as SignInRouterNextEvent)?.resource || context.clerk.client.signIn)
@@ -71,7 +73,9 @@ export const SignInRouterMachine = setup({
7173

7274
const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignInUrl());
7375
void context.clerk.setActive({ session, beforeEmit });
74-
},
76+
77+
enqueue.raise({ type: 'RESET' }, { delay: 2000 }); // Reset machine after 2s delay.
78+
}),
7579
setError: assign({
7680
error: (_, { error }: { error?: ClerkElementsError }) => {
7781
if (error) return error;
@@ -182,6 +186,7 @@ export const SignInRouterMachine = setup({
182186
},
183187
})),
184188
},
189+
RESET: '.Idle',
185190
},
186191
states: {
187192
Idle: {
@@ -221,6 +226,10 @@ export const SignInRouterMachine = setup({
221226
guard: 'needsCallback',
222227
target: 'Callback',
223228
},
229+
{
230+
guard: 'isComplete',
231+
actions: 'setActive',
232+
},
224233
{
225234
guard: 'isLoggedInAndSingleSession',
226235
actions: [
@@ -280,7 +289,6 @@ export const SignInRouterMachine = setup({
280289
{
281290
guard: 'isComplete',
282291
actions: 'setActive',
283-
target: 'Start',
284292
},
285293
{
286294
guard: 'statusNeedsFirstFactor',
@@ -318,7 +326,6 @@ export const SignInRouterMachine = setup({
318326
{
319327
guard: 'isComplete',
320328
actions: 'setActive',
321-
target: 'Start',
322329
},
323330
{
324331
guard: 'statusNeedsSecondFactor',
@@ -385,7 +392,6 @@ export const SignInRouterMachine = setup({
385392
{
386393
guard: 'isComplete',
387394
actions: 'setActive',
388-
target: 'Start',
389395
},
390396
{
391397
guard: 'statusNeedsNewPassword',
@@ -413,7 +419,6 @@ export const SignInRouterMachine = setup({
413419
{
414420
guard: 'isComplete',
415421
actions: 'setActive',
416-
target: 'Start',
417422
},
418423
{
419424
guard: 'statusNeedsFirstFactor',
@@ -439,9 +444,8 @@ export const SignInRouterMachine = setup({
439444
target: 'Start',
440445
},
441446
{
442-
guard: or(['isComplete', 'hasAuthenticatedViaClerkJS']),
447+
guard: or(['isLoggedIn', 'isComplete', 'hasAuthenticatedViaClerkJS']),
443448
actions: 'setActive',
444-
target: 'Start',
445449
},
446450
{
447451
guard: 'statusNeedsIdentifier',

packages/elements/src/internals/machines/sign-in/router.types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
BaseRouterNextEvent,
1111
BaseRouterPrevEvent,
1212
BaseRouterRedirectEvent,
13+
BaseRouterResetEvent,
1314
BaseRouterSetClerkEvent,
1415
BaseRouterStartEvent,
1516
BaseRouterTransferEvent,
@@ -54,6 +55,7 @@ export type SignInRouterForgotPasswordEvent = { type: 'NAVIGATE.FORGOT_PASSWORD'
5455
export type SignInRouterErrorEvent = BaseRouterErrorEvent;
5556
export type SignInRouterTransferEvent = BaseRouterTransferEvent;
5657
export type SignInRouterRedirectEvent = BaseRouterRedirectEvent;
58+
export type SignInRouterResetEvent = BaseRouterResetEvent;
5759
export type SignInRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'reset-password'>;
5860
export type SignInRouterSetClerkEvent = BaseRouterSetClerkEvent;
5961
export type SignInRouterSubmitEvent = { type: 'SUBMIT' };
@@ -77,6 +79,7 @@ export type SignInRouterEvents =
7779
| SignInRouterErrorEvent
7880
| SignInRouterTransferEvent
7981
| SignInRouterRedirectEvent
82+
| SignInRouterResetEvent
8083
| SignInVerificationFactorUpdateEvent
8184
| SignInRouterLoadingEvent
8285
| SignInRouterSetClerkEvent

packages/elements/src/internals/machines/sign-up/router.machine.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { joinURL } from '@clerk/shared/url';
22
import type { SignUpStatus, VerificationStatus } from '@clerk/types';
33
import type { NonReducibleUnknown } from 'xstate';
4-
import { and, assign, log, not, or, raise, sendTo, setup, spawnChild } from 'xstate';
4+
import { and, assign, enqueueActions, log, not, or, raise, sendTo, setup, spawnChild } from 'xstate';
55

66
import {
77
ERROR_CODES,
@@ -59,15 +59,20 @@ export const SignUpRouterMachine = setup({
5959
},
6060
navigateExternal: ({ context }, { path }: { path: string }) => context.router?.push(path),
6161
raiseNext: raise({ type: 'NEXT' }),
62-
setActive({ context, event }, params?: { sessionId?: string; useLastActiveSession?: boolean }) {
63-
const session =
64-
params?.sessionId ||
65-
(params?.useLastActiveSession && context.clerk.client.lastActiveSessionId) ||
66-
((event as SignUpRouterNextEvent)?.resource || context.clerk.client.signUp).createdSessionId;
62+
setActive: (_, params?: { sessionId?: string; useLastActiveSession?: boolean }) =>
63+
enqueueActions(({ enqueue, check, context, event }) => {
64+
if (check('isExampleMode')) return;
6765

68-
const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignUpUrl());
69-
void context.clerk.setActive({ session, beforeEmit });
70-
},
66+
const session =
67+
params?.sessionId ||
68+
(params?.useLastActiveSession && context.clerk.client.lastActiveSessionId) ||
69+
((event as SignUpRouterNextEvent)?.resource || context.clerk.client.signUp).createdSessionId;
70+
71+
const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignUpUrl());
72+
void context.clerk.setActive({ session, beforeEmit });
73+
74+
enqueue.raise({ type: 'RESET' }, { delay: 2000 }); // Reset machine after 2s delay.
75+
}),
7176
setError: assign({
7277
error: (_, { error }: { error?: ClerkElementsError }) => {
7378
if (error) return error;
@@ -182,6 +187,7 @@ export const SignUpRouterMachine = setup({
182187
},
183188
})),
184189
},
190+
RESET: '.Idle',
185191
},
186192
states: {
187193
Idle: {

packages/elements/src/internals/machines/sign-up/router.types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
BaseRouterNextEvent,
1111
BaseRouterPrevEvent,
1212
BaseRouterRedirectEvent,
13+
BaseRouterResetEvent,
1314
BaseRouterSetClerkEvent,
1415
BaseRouterStartEvent,
1516
BaseRouterTransferEvent,
@@ -46,6 +47,7 @@ export type SignUpRouterPrevEvent = BaseRouterPrevEvent;
4647
export type SignUpRouterErrorEvent = BaseRouterErrorEvent;
4748
export type SignUpRouterTransferEvent = BaseRouterTransferEvent;
4849
export type SignUpRouterRedirectEvent = BaseRouterRedirectEvent;
50+
export type SignUpRouterResetEvent = BaseRouterResetEvent;
4951
export type SignUpRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'continue'>;
5052
export type SignUpRouterSetClerkEvent = BaseRouterSetClerkEvent;
5153

@@ -64,6 +66,7 @@ export type SignUpRouterEvents =
6466
| SignUpRouterErrorEvent
6567
| SignUpRouterTransferEvent
6668
| SignUpRouterRedirectEvent
69+
| SignUpRouterResetEvent
6770
| SignUpRouterLoadingEvent
6871
| SignUpRouterSetClerkEvent;
6972

packages/elements/src/internals/machines/types/router.types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type BaseRouterLoadingStep = 'start' | 'verifications' | 'continue' | 're
1717
export type BaseRouterNextEvent<T extends ClerkResource> = { type: 'NEXT'; resource?: T };
1818
export type BaseRouterPrevEvent = { type: 'NAVIGATE.PREVIOUS' };
1919
export type BaseRouterStartEvent = { type: 'NAVIGATE.START' };
20+
export type BaseRouterResetEvent = { type: 'RESET' };
2021
export type BaseRouterErrorEvent = { type: 'ERROR'; error: Error };
2122
export type BaseRouterTransferEvent = { type: 'TRANSFER' };
2223
export type BaseRouterLoadingEvent<TSteps extends BaseRouterLoadingStep> = (

packages/elements/src/react/sign-in/root.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ function SignInFlowProvider({ children, exampleMode }: SignInFlowProviderProps)
4444
actor.send(evt);
4545
}
4646
});
47-
}, [clerk, exampleMode, formRef, router]);
47+
48+
// eslint-disable-next-line react-hooks/exhaustive-deps
49+
}, [clerk, exampleMode, formRef?.id, !!router]);
4850

4951
return <SignInRouterCtx.Provider actorRef={actor}>{children}</SignInRouterCtx.Provider>;
5052
}
@@ -94,6 +96,7 @@ export function SignInRoot({
9496

9597
// TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js
9698
const router = useNextRouter();
99+
const isRootPath = path === router.pathname();
97100

98101
return (
99102
<Router
@@ -102,9 +105,11 @@ export function SignInRoot({
102105
>
103106
<FormStoreProvider>
104107
<SignInFlowProvider exampleMode={exampleMode}>
105-
<ClerkLoading>
106-
<Form>{fallback}</Form>
107-
</ClerkLoading>
108+
{isRootPath ? (
109+
<ClerkLoading>
110+
<Form>{fallback}</Form>
111+
</ClerkLoading>
112+
) : null}
108113
<ClerkLoaded>{children}</ClerkLoaded>
109114
</SignInFlowProvider>
110115
</FormStoreProvider>

packages/elements/src/react/sign-in/verifications.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export function SignInStrategy({ children, name }: SignInStrategyProps) {
8080
}
8181

8282
return () => {
83-
if (factorCtx) {
83+
if (factorCtx?.getSnapshot().status === 'active') {
8484
factorCtx.send({ type: 'STRATEGY.UNREGISTER', factor: name as unknown as SignInFactor });
8585
}
8686
};

packages/elements/src/react/sign-up/root.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ function SignUpFlowProvider({ children, exampleMode }: SignUpFlowProviderProps)
4646
ref.send(evt);
4747
}
4848
});
49-
}, [clerk, exampleMode, formRef, router]);
49+
// eslint-disable-next-line react-hooks/exhaustive-deps
50+
}, [clerk, exampleMode, formRef?.id, !!router]);
5051

5152
return isReady ? <SignUpRouterCtx.Provider actorRef={ref}>{children}</SignUpRouterCtx.Provider> : null;
5253
}
@@ -91,6 +92,7 @@ export function SignUpRoot({
9192

9293
// TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js
9394
const router = useNextRouter();
95+
const isRootPath = path === router.pathname();
9496

9597
return (
9698
<Router
@@ -99,9 +101,11 @@ export function SignUpRoot({
99101
>
100102
<FormStoreProvider>
101103
<SignUpFlowProvider exampleMode={exampleMode}>
102-
<ClerkLoading>
103-
<Form>{fallback}</Form>
104-
</ClerkLoading>
104+
{isRootPath ? (
105+
<ClerkLoading>
106+
<Form>{fallback}</Form>
107+
</ClerkLoading>
108+
) : null}
105109
<ClerkLoaded>{children}</ClerkLoaded>
106110
</SignUpFlowProvider>
107111
</FormStoreProvider>

0 commit comments

Comments
 (0)