diff --git a/.changeset/itchy-mirrors-decide.md b/.changeset/itchy-mirrors-decide.md new file mode 100644 index 0000000000..dfd472eed6 --- /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-up/[[...sign-up]]/page.tsx b/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx index 1b9197f9c0..e9c4538028 100644 --- a/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx +++ b/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx @@ -2,6 +2,7 @@ import * as Clerk from '@clerk/elements/common'; import * as SignUp from '@clerk/elements/sign-up'; +import Link from 'next/link'; import type { ComponentProps } from 'react'; import { H1, HR as Hr, P } from '@/components/design'; @@ -42,6 +43,16 @@ export default function SignUpPage() {

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: { @@ -292,6 +303,10 @@ export const SignInRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'Start', + reenter: true, + }, NEXT: [ { guard: 'isComplete', @@ -329,6 +344,10 @@ export const SignInRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'FirstFactor', + reenter: true, + }, NEXT: [ { guard: 'isComplete', @@ -399,6 +418,10 @@ export const SignInRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'SecondFactor', + reenter: true, + }, NEXT: [ { guard: 'isComplete', @@ -426,6 +449,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 ada057c95c..d1ea1209ea 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 79503b4539..8f67f4953c 100644 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/router.machine.ts @@ -181,6 +181,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: { @@ -274,6 +285,10 @@ export const SignUpRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'Start', + reenter: true, + }, NEXT: [ { guard: 'isStatusComplete', @@ -307,6 +322,10 @@ export const SignUpRouterMachine = setup({ }, }, on: { + 'RESET.STEP': { + target: 'Continue', + reenter: true, + }, NEXT: [ { guard: 'isStatusComplete', @@ -355,6 +374,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 2ea4fd4b0b..cd0b69a8bd 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 904fb56dd4..f28ad58477 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 b33fa50a69..09cce2c872 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 a3c25da212..4a0cc57ad7 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 = SignUpFlowProviderProps & {