From 8cef2e5f8305142157166453ce29e7ed26c58c6f Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 30 May 2024 15:17:27 -0400 Subject: [PATCH 1/2] fix(elements): Forms unable to submit on re-mount --- .changeset/itchy-mirrors-decide.md | 5 ++++ .../app/sign-in/[[...sign-in]]/page.tsx | 2 +- .../app/sign-up/[[...sign-up]]/page.tsx | 21 +++++++++++++++ .../machines/sign-in/router.machine.ts | 27 +++++++++++++++++++ .../machines/sign-in/router.types.ts | 6 +++++ .../machines/sign-up/router.machine.ts | 23 ++++++++++++++++ .../machines/sign-up/router.types.ts | 6 +++++ .../internals/machines/types/router.types.ts | 4 +++ packages/elements/src/react/sign-in/root.tsx | 8 ++++++ packages/elements/src/react/sign-up/root.tsx | 18 +++++++++---- 10 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 .changeset/itchy-mirrors-decide.md diff --git a/.changeset/itchy-mirrors-decide.md b/.changeset/itchy-mirrors-decide.md new file mode 100644 index 00000000000..dfd472eed6f --- /dev/null +++ b/.changeset/itchy-mirrors-decide.md @@ -0,0 +1,5 @@ +--- +'@clerk/elements': patch +--- + +Fix forms unable to submit upon re-mounting diff --git a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx index dfaafd36a21..acedc0d959a 100644 --- a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx +++ b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx @@ -93,7 +93,7 @@ function CustomResendable() { } export default function SignInPage() { - const [continueWithEmail, setContinueWithEmail] = useState(false); + const [continueWithEmail, setContinueWithEmail] = useState(true); return (

Sign Up

+

+ Have an account?{' '} + + Sign In + +

+

Sign Up

+

+ Have an account?{' '} + + Sign In + +

+
{ + enqueue.assign({ + formRef: event.formRef, + }); + + // Reset the current step, to reset the form reference. + enqueue.raise({ type: 'RESET.STEP' }); + }), + }, 'NAVIGATE.PREVIOUS': '.Hist', 'NAVIGATE.START': '.Start', LOADING: { @@ -285,6 +296,10 @@ export const SignInRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'Start', + reenter: true, + }, NEXT: [ { guard: 'isComplete', @@ -322,6 +337,10 @@ export const SignInRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'FirstFactor', + reenter: true, + }, NEXT: [ { guard: 'isComplete', @@ -392,6 +411,10 @@ export const SignInRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'SecondFactor', + reenter: true, + }, NEXT: [ { guard: 'isComplete', @@ -419,6 +442,10 @@ export const SignInRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'ResetPassword', + reenter: true, + }, NEXT: [ { guard: 'isComplete', diff --git a/packages/elements/src/internals/machines/sign-in/router.types.ts b/packages/elements/src/internals/machines/sign-in/router.types.ts index ada057c95c9..d1ea1209eab 100644 --- a/packages/elements/src/internals/machines/sign-in/router.types.ts +++ b/packages/elements/src/internals/machines/sign-in/router.types.ts @@ -5,12 +5,14 @@ import type { TFormMachine } from '~/internals/machines/form'; import type { BaseRouterContext, BaseRouterErrorEvent, + BaseRouterFormAttachEvent, BaseRouterInput, BaseRouterLoadingEvent, BaseRouterNextEvent, BaseRouterPrevEvent, BaseRouterRedirectEvent, BaseRouterResetEvent, + BaseRouterResetStepEvent, BaseRouterSetClerkEvent, BaseRouterStartEvent, BaseRouterTransferEvent, @@ -47,6 +49,7 @@ export type SignInRouterSystemId = keyof typeof SignInRouterSystemId; // ---------------------------------- Events ---------------------------------- // +export type SignInRouterFormAttachEvent = BaseRouterFormAttachEvent; export type SignInRouterNextEvent = BaseRouterNextEvent; export type SignInRouterStartEvent = BaseRouterStartEvent; export type SignInRouterPrevEvent = BaseRouterPrevEvent; @@ -56,6 +59,7 @@ export type SignInRouterErrorEvent = BaseRouterErrorEvent; export type SignInRouterTransferEvent = BaseRouterTransferEvent; export type SignInRouterRedirectEvent = BaseRouterRedirectEvent; export type SignInRouterResetEvent = BaseRouterResetEvent; +export type SignInRouterResetStepEvent = BaseRouterResetStepEvent; export type SignInRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'reset-password'>; export type SignInRouterSetClerkEvent = BaseRouterSetClerkEvent; export type SignInRouterSubmitEvent = { type: 'SUBMIT' }; @@ -73,6 +77,7 @@ export type SignInRouterNavigationEvents = | SignInRouterPrevEvent; export type SignInRouterEvents = + | SignInRouterFormAttachEvent | SignInRouterInitEvent | SignInRouterNextEvent | SignInRouterNavigationEvents @@ -80,6 +85,7 @@ export type SignInRouterEvents = | SignInRouterTransferEvent | SignInRouterRedirectEvent | SignInRouterResetEvent + | SignInRouterResetStepEvent | SignInVerificationFactorUpdateEvent | SignInRouterLoadingEvent | SignInRouterSetClerkEvent diff --git a/packages/elements/src/internals/machines/sign-up/router.machine.ts b/packages/elements/src/internals/machines/sign-up/router.machine.ts index 88845dd2168..9d7fe5ca7c2 100644 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/router.machine.ts @@ -174,6 +174,17 @@ export const SignUpRouterMachine = setup({ params: { strategy: 'saml' }, }), }, + 'FORM.ATTACH': { + description: 'Attach/re-attach the form to the router.', + actions: enqueueActions(({ enqueue, event }) => { + enqueue.assign({ + formRef: event.formRef, + }); + + // Reset the current step, to reset the form reference. + enqueue.raise({ type: 'RESET.STEP' }); + }), + }, 'NAVIGATE.PREVIOUS': '.Hist', 'NAVIGATE.START': '.Start', LOADING: { @@ -267,6 +278,10 @@ export const SignUpRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'Start', + reenter: true, + }, NEXT: [ { guard: 'isStatusComplete', @@ -300,6 +315,10 @@ export const SignUpRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'Continue', + reenter: true, + }, NEXT: [ { guard: 'isStatusComplete', @@ -348,6 +367,10 @@ export const SignUpRouterMachine = setup({ }, ], on: { + 'RESET.STEP': { + target: 'Verification', + reenter: true, + }, NEXT: [ { guard: 'isStatusComplete', diff --git a/packages/elements/src/internals/machines/sign-up/router.types.ts b/packages/elements/src/internals/machines/sign-up/router.types.ts index 2ea4fd4b0b6..cd0b69a8bd6 100644 --- a/packages/elements/src/internals/machines/sign-up/router.types.ts +++ b/packages/elements/src/internals/machines/sign-up/router.types.ts @@ -5,12 +5,14 @@ import type { TFormMachine } from '~/internals/machines/form'; import type { BaseRouterContext, BaseRouterErrorEvent, + BaseRouterFormAttachEvent, BaseRouterInput, BaseRouterLoadingEvent, BaseRouterNextEvent, BaseRouterPrevEvent, BaseRouterRedirectEvent, BaseRouterResetEvent, + BaseRouterResetStepEvent, BaseRouterSetClerkEvent, BaseRouterStartEvent, BaseRouterTransferEvent, @@ -41,6 +43,7 @@ export type SignUpRouterSystemId = keyof typeof SignUpRouterSystemId; // ---------------------------------- Events ---------------------------------- // +export type SignUpRouterFormAttachEvent = BaseRouterFormAttachEvent; export type SignUpRouterNextEvent = BaseRouterNextEvent; export type SignUpRouterStartEvent = BaseRouterStartEvent; export type SignUpRouterPrevEvent = BaseRouterPrevEvent; @@ -48,6 +51,7 @@ export type SignUpRouterErrorEvent = BaseRouterErrorEvent; export type SignUpRouterTransferEvent = BaseRouterTransferEvent; export type SignUpRouterRedirectEvent = BaseRouterRedirectEvent; export type SignUpRouterResetEvent = BaseRouterResetEvent; +export type SignUpRouterResetStepEvent = BaseRouterResetStepEvent; export type SignUpRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'continue'>; export type SignUpRouterSetClerkEvent = BaseRouterSetClerkEvent; @@ -60,6 +64,7 @@ export interface SignUpRouterInitEvent extends BaseRouterInput { export type SignUpRouterNavigationEvents = SignUpRouterStartEvent | SignUpRouterPrevEvent; export type SignUpRouterEvents = + | SignUpRouterFormAttachEvent | SignUpRouterInitEvent | SignUpRouterNextEvent | SignUpRouterNavigationEvents @@ -67,6 +72,7 @@ export type SignUpRouterEvents = | SignUpRouterTransferEvent | SignUpRouterRedirectEvent | SignUpRouterResetEvent + | SignUpRouterResetStepEvent | SignUpRouterLoadingEvent | SignUpRouterSetClerkEvent; diff --git a/packages/elements/src/internals/machines/types/router.types.ts b/packages/elements/src/internals/machines/types/router.types.ts index 904fb56dd4e..f28ad584774 100644 --- a/packages/elements/src/internals/machines/types/router.types.ts +++ b/packages/elements/src/internals/machines/types/router.types.ts @@ -6,8 +6,10 @@ import type { SignInStrategy, Web3Strategy, } from '@clerk/types'; +import type { ActorRefFrom } from 'xstate'; import type { ClerkElementsError } from '~/internals/errors'; +import type { TFormMachine } from '~/internals/machines/form'; import type { ClerkRouter } from '~/react/router'; // ---------------------------------- Events ---------------------------------- // @@ -15,9 +17,11 @@ import type { ClerkRouter } from '~/react/router'; export type BaseRouterLoadingStep = 'start' | 'verifications' | 'continue' | 'reset-password'; export type BaseRouterNextEvent = { type: 'NEXT'; resource?: T }; +export type BaseRouterFormAttachEvent = { type: 'FORM.ATTACH'; formRef: ActorRefFrom }; export type BaseRouterPrevEvent = { type: 'NAVIGATE.PREVIOUS' }; export type BaseRouterStartEvent = { type: 'NAVIGATE.START' }; export type BaseRouterResetEvent = { type: 'RESET' }; +export type BaseRouterResetStepEvent = { type: 'RESET.STEP' }; export type BaseRouterErrorEvent = { type: 'ERROR'; error: Error }; export type BaseRouterTransferEvent = { type: 'TRANSFER' }; export type BaseRouterLoadingEvent = ( diff --git a/packages/elements/src/react/sign-in/root.tsx b/packages/elements/src/react/sign-in/root.tsx index ad0dc420cc0..e39af0f723e 100644 --- a/packages/elements/src/react/sign-in/root.tsx +++ b/packages/elements/src/react/sign-in/root.tsx @@ -45,6 +45,14 @@ function SignInFlowProvider({ children, exampleMode }: SignInFlowProviderProps) } }); + // Ensure that the latest instantiated formRef is attached to the router + if (formRef && actor.getSnapshot().can({ type: 'RESET.STEP' })) { + actor.send({ + type: 'FORM.ATTACH', + formRef, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [clerk, exampleMode, formRef?.id, !!router]); diff --git a/packages/elements/src/react/sign-up/root.tsx b/packages/elements/src/react/sign-up/root.tsx index 0eea9bf0dfd..f6d191a90e6 100644 --- a/packages/elements/src/react/sign-up/root.tsx +++ b/packages/elements/src/react/sign-up/root.tsx @@ -20,13 +20,13 @@ type SignUpFlowProviderProps = { }; const actor = createActor(SignUpRouterMachine, { inspect }); -const ref = actor.start(); +actor.start(); function SignUpFlowProvider({ children, exampleMode }: SignUpFlowProviderProps) { const clerk = useClerk(); const router = useClerkRouter(); const formRef = useFormStore(); - const isReady = useSelector(ref, state => state.value !== 'Idle'); + const isReady = useSelector(actor, state => state.value !== 'Idle'); useEffect(() => { if (!clerk || !router) return; @@ -42,14 +42,22 @@ function SignUpFlowProvider({ children, exampleMode }: SignUpFlowProviderProps) signInPath: SIGN_IN_DEFAULT_BASE_PATH, }; - if (ref.getSnapshot().can(evt)) { - ref.send(evt); + if (actor.getSnapshot().can(evt)) { + actor.send(evt); + } + + // Ensure that the latest instantiated formRef is attached to the router + if (formRef && actor.getSnapshot().can({ type: 'RESET.STEP' })) { + actor.send({ + type: 'FORM.ATTACH', + formRef, + }); } }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [clerk, exampleMode, formRef?.id, !!router]); - return isReady ? {children} : null; + return isReady ? {children} : null; } export type SignUpRootProps = { From 066fafdc6406b30d2c1a092206fcbf356027138c Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 30 May 2024 15:20:09 -0400 Subject: [PATCH 2/2] chore(elements): Revert continueWithEmail initial state --- .../examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx index acedc0d959a..dfaafd36a21 100644 --- a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx +++ b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx @@ -93,7 +93,7 @@ function CustomResendable() { } export default function SignInPage() { - const [continueWithEmail, setContinueWithEmail] = useState(true); + const [continueWithEmail, setContinueWithEmail] = useState(false); return (