@quiltt/react 4.1.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @quiltt/react
2
2
 
3
+ ## 4.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#350](https://github.com/quiltt/quiltt-js/pull/350) [`0233592`](https://github.com/quiltt/quiltt-js/commit/02335928bb872a6588c2ca81a1bd9a081053bd29) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Bugfix: early return in effect prevents reconnect from being called
8
+
9
+ - Updated dependencies [[`0233592`](https://github.com/quiltt/quiltt-js/commit/02335928bb872a6588c2ca81a1bd9a081053bd29)]:
10
+ - @quiltt/core@4.2.1
11
+
12
+ ## 4.2.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [#348](https://github.com/quiltt/quiltt-js/pull/348) [`7e27845`](https://github.com/quiltt/quiltt-js/commit/7e2784523124c87fc6654d8336f924286daade1b) Thanks [@zubairaziz](https://github.com/zubairaziz)! - resolve connectionId persistence issue in QuilttContainer
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [[`7e27845`](https://github.com/quiltt/quiltt-js/commit/7e2784523124c87fc6654d8336f924286daade1b)]:
21
+ - @quiltt/core@4.2.0
22
+
3
23
  ## 4.1.0
4
24
 
5
25
  ### Minor Changes
@@ -6,7 +6,7 @@ import './QuilttSettings-client-BK-0SQME.js';
6
6
  import './useSession-client-CCAvnROP.js';
7
7
  import { jsx } from 'react/jsx-runtime';
8
8
  import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.js';
9
- import { u as useQuilttSession } from './useQuilttSession-client-D2mjVT4S.js';
9
+ import { u as useQuilttSession } from './useQuilttSession-client-Ddb55W0n.js';
10
10
 
11
11
  const useIdentifySession = (auth, setSession)=>{
12
12
  const identifySession = useCallback(async (payload, callbacks)=>{
@@ -187,4 +187,4 @@ const useRevokeSession = (auth, session, setSession)=>{
187
187
  });
188
188
  };
189
189
 
190
- export { QuilttAuthProvider as Q, useIdentifySession as a, useAuthenticateSession as b, useRevokeSession as c, useImportSession as u };
190
+ export { QuilttAuthProvider as Q, useIdentifySession as a, useAuthenticateSession as b, useRevokeSession as c, isDeepEqual as i, useImportSession as u };
package/dist/index.d.ts CHANGED
@@ -133,6 +133,12 @@ type BaseQuilttButtonProps<T extends ElementType> = {
133
133
  connectorId: string;
134
134
  connectionId?: string;
135
135
  institution?: string;
136
+ /**
137
+ * Forces complete remount when connectionId changes.
138
+ * Useful as a fallback for ensuring clean state.
139
+ * @default false
140
+ */
141
+ forceRemountOnConnectionChange?: boolean;
136
142
  onClick?: (event: MouseEvent<HTMLElement>) => void;
137
143
  };
138
144
  type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
@@ -140,18 +146,35 @@ type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
140
146
  onHtmlLoad?: React.ReactEventHandler<HTMLElement>;
141
147
  };
142
148
  type QuilttButtonProps<T extends ElementType> = PropsWithChildren<BaseQuilttButtonProps<T> & QuilttCallbackProps>;
143
- declare const QuilttButton: <T extends ElementType = "button">({ as, connectorId, connectionId, institution, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props }: QuilttButtonProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
149
+ /**
150
+ * QuilttButton provides a clickable interface to open Quiltt connectors.
151
+ *
152
+ * When connectionId changes, the button will automatically update the existing
153
+ * connector instance with the new connection details. If you need to force a
154
+ * complete remount instead, set forceRemountOnConnectionChange to true.
155
+ */
156
+ declare const QuilttButton: <T extends ElementType = "button">({ as, connectorId, connectionId, institution, forceRemountOnConnectionChange, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props }: QuilttButtonProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
144
157
 
145
158
  type QuilttContainerProps<T extends ElementType> = PropsWithChildren<{
146
159
  as?: T;
147
160
  connectorId: string;
148
161
  connectionId?: string;
162
+ /**
163
+ * Forces complete remount when connectionId changes.
164
+ * Useful as a fallback for ensuring clean state.
165
+ * @default false
166
+ */
167
+ forceRemountOnConnectionChange?: boolean;
149
168
  } & ConnectorSDKCallbacks>;
150
169
  /**
151
170
  * QuilttContainer uses globally shared callbacks. It's recommended you only use
152
171
  * one Container at a time.
172
+ *
173
+ * When connectionId changes, the container will automatically update the existing
174
+ * connector instance with the new connection details. If you need to force a
175
+ * complete remount instead, set forceRemountOnConnectionChange to true.
153
176
  */
154
- declare const QuilttContainer: <T extends ElementType = "div">({ as, connectorId, connectionId, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props }: QuilttContainerProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
177
+ declare const QuilttContainer: <T extends ElementType = "div">({ as, connectorId, connectionId, forceRemountOnConnectionChange, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props }: QuilttContainerProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
155
178
 
156
179
  export { QuilttAuthProvider, QuilttButton, QuilttContainer, QuilttProvider, QuilttSettingsProvider, useAuthenticateSession, useEventListener, useIdentifySession, useImportSession, useIsomorphicLayoutEffect, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useRevokeSession, useSession, useStorage };
157
180
  export type { AuthenticateSession, IdentifySession, ImportSession, RevokeSession, SetSession, UseQuilttSession };
package/dist/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  export * from '@quiltt/core';
2
2
  export { u as useEventListener } from './useEventListener-client-DVM5xwKY.js';
3
3
  export { u as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect-client-DeTHOKz1.js';
4
- import { Q as QuilttAuthProvider } from './QuilttAuthProvider-client-CER-TOln.js';
5
- export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-CER-TOln.js';
4
+ import { Q as QuilttAuthProvider, i as isDeepEqual } from './QuilttAuthProvider-client-DLARZukU.js';
5
+ export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-DLARZukU.js';
6
6
  export { u as useQuilttClient } from './useQuilttClient-client-CAAUait1.js';
7
- import { u as useQuilttConnector } from './useQuilttConnector-client-BB7kw8vs.js';
8
- export { u as useQuilttSession } from './useQuilttSession-client-D2mjVT4S.js';
7
+ import { u as useQuilttConnector } from './useQuilttConnector-client-CacEgWJN.js';
8
+ export { u as useQuilttSession } from './useQuilttSession-client-Ddb55W0n.js';
9
9
  export { u as useQuilttSettings } from './useQuilttSettings-client-BOCBjFXe.js';
10
10
  export { u as useSession } from './useSession-client-CCAvnROP.js';
11
11
  export { u as useStorage } from './useStorage-client-DHcq3Kuh.js';
12
12
  import { jsx } from 'react/jsx-runtime';
13
13
  import { Q as QuilttSettingsProvider } from './QuilttSettingsProvider-client-Va7uJ_dQ.js';
14
+ import { useRef, useEffect } from 'react';
14
15
 
15
16
  const QuilttProvider = ({ clientId, token, children })=>{
16
17
  return /*#__PURE__*/ jsx(QuilttSettingsProvider, {
@@ -22,7 +23,46 @@ const QuilttProvider = ({ clientId, token, children })=>{
22
23
  });
23
24
  };
24
25
 
25
- const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props })=>{
26
+ /**
27
+ * QuilttButton provides a clickable interface to open Quiltt connectors.
28
+ *
29
+ * When connectionId changes, the button will automatically update the existing
30
+ * connector instance with the new connection details. If you need to force a
31
+ * complete remount instead, set forceRemountOnConnectionChange to true.
32
+ */ const QuilttButton = ({ as, connectorId, connectionId, institution, forceRemountOnConnectionChange = false, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props })=>{
33
+ // Keep track of previous connectionId for change detection
34
+ const prevConnectionIdRef = useRef(connectionId);
35
+ const prevCallbacksRef = useRef({
36
+ onEvent,
37
+ onOpen,
38
+ onLoad,
39
+ onExit,
40
+ onExitSuccess,
41
+ onExitAbort,
42
+ onExitError
43
+ });
44
+ // Track if callbacks have changed to help with debugging
45
+ const currentCallbacks = {
46
+ onEvent,
47
+ onOpen,
48
+ onLoad,
49
+ onExit,
50
+ onExitSuccess,
51
+ onExitAbort,
52
+ onExitError
53
+ };
54
+ const callbacksChanged = !isDeepEqual(prevCallbacksRef.current, currentCallbacks);
55
+ useEffect(()=>{
56
+ prevCallbacksRef.current = currentCallbacks;
57
+ });
58
+ // Warning for potential callback reference issues
59
+ useEffect(()=>{
60
+ if (callbacksChanged && prevConnectionIdRef.current !== undefined) {
61
+ console.warn('[Quiltt] Callback functions changed after initial render. ' + 'This may cause unexpected behavior. Consider memoizing callback functions ' + 'with useCallback to maintain stable references.');
62
+ }
63
+ }, [
64
+ callbacksChanged
65
+ ]);
26
66
  const { open } = useQuilttConnector(connectorId, {
27
67
  connectionId,
28
68
  institution,
@@ -35,6 +75,12 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
35
75
  onExitAbort,
36
76
  onExitError
37
77
  });
78
+ // Update previous connectionId reference
79
+ useEffect(()=>{
80
+ prevConnectionIdRef.current = connectionId;
81
+ }, [
82
+ connectionId
83
+ ]);
38
84
  const Button = as || 'button';
39
85
  const handleClick = (event)=>{
40
86
  // Call the user's onClick handler first to allow for:
@@ -45,20 +91,58 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
45
91
  // Then open the Quiltt connector
46
92
  open();
47
93
  };
94
+ // Generate key for forced remounting if enabled, but respect user-provided key
95
+ const buttonKey = props.key ?? (forceRemountOnConnectionChange ? `${connectorId}-${connectionId || 'no-connection'}` : undefined);
48
96
  return /*#__PURE__*/ jsx(Button, {
49
97
  onClick: handleClick,
50
98
  onLoad: onHtmlLoad,
51
99
  "quiltt-connection": connectionId,
52
100
  ...props,
53
101
  children: children
54
- });
102
+ }, buttonKey);
55
103
  };
56
104
 
57
105
  /**
58
106
  * QuilttContainer uses globally shared callbacks. It's recommended you only use
59
107
  * one Container at a time.
60
- */ const QuilttContainer = ({ as, connectorId, connectionId, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props })=>{
108
+ *
109
+ * When connectionId changes, the container will automatically update the existing
110
+ * connector instance with the new connection details. If you need to force a
111
+ * complete remount instead, set forceRemountOnConnectionChange to true.
112
+ */ const QuilttContainer = ({ as, connectorId, connectionId, forceRemountOnConnectionChange = false, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props })=>{
113
+ // Keep track of previous connectionId for change detection
114
+ const prevConnectionIdRef = useRef(connectionId);
115
+ const prevCallbacksRef = useRef({
116
+ onEvent,
117
+ onLoad,
118
+ onExit,
119
+ onExitSuccess,
120
+ onExitAbort,
121
+ onExitError
122
+ });
123
+ // Track if callbacks have changed to help with debugging
124
+ const currentCallbacks = {
125
+ onEvent,
126
+ onLoad,
127
+ onExit,
128
+ onExitSuccess,
129
+ onExitAbort,
130
+ onExitError
131
+ };
132
+ const callbacksChanged = !isDeepEqual(prevCallbacksRef.current, currentCallbacks);
133
+ useEffect(()=>{
134
+ prevCallbacksRef.current = currentCallbacks;
135
+ });
136
+ // Warning for potential callback reference issues
137
+ useEffect(()=>{
138
+ if (callbacksChanged && prevConnectionIdRef.current !== undefined) {
139
+ console.warn('[Quiltt] Callback functions changed after initial render. ' + 'This may cause unexpected behavior. Consider memoizing callback functions ' + 'with useCallback to maintain stable references.');
140
+ }
141
+ }, [
142
+ callbacksChanged
143
+ ]);
61
144
  useQuilttConnector(connectorId, {
145
+ connectionId,
62
146
  nonce: props?.nonce,
63
147
  onEvent,
64
148
  onLoad,
@@ -67,13 +151,21 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
67
151
  onExitAbort,
68
152
  onExitError
69
153
  });
154
+ // Update previous connectionId reference
155
+ useEffect(()=>{
156
+ prevConnectionIdRef.current = connectionId;
157
+ }, [
158
+ connectionId
159
+ ]);
70
160
  const Container = as || 'div';
161
+ // Generate key for forced remounting if enabled, but respect user-provided key
162
+ const containerKey = props.key ?? (forceRemountOnConnectionChange ? `${connectorId}-${connectionId || 'no-connection'}` : undefined);
71
163
  return /*#__PURE__*/ jsx(Container, {
72
164
  "quiltt-container": connectorId,
73
165
  "quiltt-connection": connectionId,
74
166
  ...props,
75
167
  children: children
76
- });
168
+ }, containerKey);
77
169
  };
78
170
 
79
171
  export { QuilttAuthProvider, QuilttButton, QuilttContainer, QuilttProvider, QuilttSettingsProvider, useQuilttConnector };
@@ -1,10 +1,10 @@
1
1
  'use client';
2
- import { useState, useEffect, useCallback } from 'react';
2
+ import { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { cdnBase } from '@quiltt/core';
4
- import { u as useQuilttSession } from './useQuilttSession-client-D2mjVT4S.js';
4
+ import { u as useQuilttSession } from './useQuilttSession-client-Ddb55W0n.js';
5
5
  import { u as useScript } from './useScript-client-JCgaTW9n.js';
6
6
 
7
- var version = "4.1.0";
7
+ var version = "4.2.1";
8
8
 
9
9
  const useQuilttConnector = (connectorId, options)=>{
10
10
  const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`, {
@@ -15,8 +15,12 @@ const useQuilttConnector = (connectorId, options)=>{
15
15
  const session = useQuilttSessionReturn?.session || null;
16
16
  const [connector, setConnector] = useState();
17
17
  const [isOpening, setIsOpening] = useState(false);
18
+ // Keep track of the previous connectionId to detect changes
19
+ const prevConnectionIdRef = useRef(options?.connectionId);
20
+ const prevConnectorIdRef = useRef(connectorId);
21
+ const connectorCreatedRef = useRef(false);
18
22
  // Set Session
19
- // biome-ignore lint/correctness/useExhaustiveDependencies: We also need to update on status change
23
+ // biome-ignore lint/correctness/useExhaustiveDependencies: trigger effects when script status changes too
20
24
  useEffect(()=>{
21
25
  if (typeof Quiltt === 'undefined') return;
22
26
  Quiltt.authenticate(session?.token);
@@ -25,23 +29,38 @@ const useQuilttConnector = (connectorId, options)=>{
25
29
  session?.token
26
30
  ]);
27
31
  // Set Connector
28
- // biome-ignore lint/correctness/useExhaustiveDependencies: We also need to update on status change
32
+ // biome-ignore lint/correctness/useExhaustiveDependencies: trigger effects when script status changes too
29
33
  useEffect(()=>{
30
34
  if (typeof Quiltt === 'undefined' || !connectorId) return;
31
- if (options?.connectionId) {
32
- setConnector(Quiltt.reconnect(connectorId, {
33
- connectionId: options.connectionId
34
- }));
35
- } else {
36
- setConnector(Quiltt.connect(connectorId, {
37
- institution: options?.institution
38
- }));
35
+ const currentConnectionId = options?.connectionId;
36
+ const currentInstitution = options?.institution;
37
+ // Check for changes
38
+ const connectionIdChanged = prevConnectionIdRef.current !== currentConnectionId;
39
+ const connectorIdChanged = prevConnectorIdRef.current !== connectorId;
40
+ const hasChanges = connectionIdChanged || connectorIdChanged || !connectorCreatedRef.current;
41
+ // Update if there are changes, regardless of what the changes are
42
+ if (hasChanges) {
43
+ if (currentConnectionId) {
44
+ // Always use reconnect when connectionId is available
45
+ setConnector(Quiltt.reconnect(connectorId, {
46
+ connectionId: currentConnectionId
47
+ }));
48
+ } else {
49
+ // Use connect for new connections without connectionId
50
+ setConnector(Quiltt.connect(connectorId, {
51
+ institution: currentInstitution
52
+ }));
53
+ }
54
+ // Update refs
55
+ connectorCreatedRef.current = true;
56
+ prevConnectionIdRef.current = currentConnectionId;
57
+ prevConnectorIdRef.current = connectorId;
39
58
  }
40
59
  }, [
41
- status,
42
60
  connectorId,
43
61
  options?.connectionId,
44
- options?.institution
62
+ options?.institution,
63
+ status
45
64
  ]);
46
65
  // onEvent
47
66
  useEffect(()=>{
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { useCallback } from 'react';
3
3
  import { AuthAPI } from '@quiltt/core';
4
- import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-client-CER-TOln.js';
4
+ import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-client-DLARZukU.js';
5
5
  import { u as useQuilttSettings } from './useQuilttSettings-client-BOCBjFXe.js';
6
6
  import { u as useSession } from './useSession-client-CCAvnROP.js';
7
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltt/react",
3
- "version": "4.1.0",
3
+ "version": "4.2.1",
4
4
  "description": "React Components and Hooks for Quiltt Connector",
5
5
  "keywords": [
6
6
  "quiltt",
@@ -36,18 +36,18 @@
36
36
  "dependencies": {
37
37
  "@apollo/client": "^3.12.4",
38
38
  "use-debounce": "^10.0.4",
39
- "@quiltt/core": "4.1.0"
39
+ "@quiltt/core": "4.2.1"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@biomejs/biome": "1.9.4",
43
- "@types/node": "22.14.1",
43
+ "@types/node": "22.15.31",
44
44
  "@types/react": "18.3.20",
45
45
  "@types/react-dom": "18.3.5",
46
46
  "bunchee": "6.3.4",
47
47
  "react": "18.3.1",
48
48
  "react-dom": "18.3.1",
49
49
  "rimraf": "6.0.1",
50
- "typescript": "5.8.2"
50
+ "typescript": "5.8.3"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -1,9 +1,11 @@
1
+ import { useEffect, useRef } from 'react'
1
2
  import type { ElementType, MouseEvent, PropsWithChildren } from 'react'
2
3
 
3
4
  import type { ConnectorSDKCallbacks } from '@quiltt/core'
4
5
 
5
6
  import { useQuilttConnector } from '@/hooks/useQuilttConnector'
6
7
  import type { PropsOf } from '@/types'
8
+ import { isDeepEqual } from '@/utils/isDeepEqual'
7
9
 
8
10
  // Base button props without callback-specific properties
9
11
  type BaseQuilttButtonProps<T extends ElementType> = {
@@ -11,6 +13,14 @@ type BaseQuilttButtonProps<T extends ElementType> = {
11
13
  connectorId: string
12
14
  connectionId?: string // For Reconnect Mode
13
15
  institution?: string // For Connect Mode
16
+
17
+ /**
18
+ * Forces complete remount when connectionId changes.
19
+ * Useful as a fallback for ensuring clean state.
20
+ * @default false
21
+ */
22
+ forceRemountOnConnectionChange?: boolean
23
+
14
24
  // Override the native onClick handler
15
25
  onClick?: (event: MouseEvent<HTMLElement>) => void
16
26
  }
@@ -27,11 +37,19 @@ type QuilttButtonProps<T extends ElementType> = PropsWithChildren<
27
37
  BaseQuilttButtonProps<T> & QuilttCallbackProps
28
38
  >
29
39
 
40
+ /**
41
+ * QuilttButton provides a clickable interface to open Quiltt connectors.
42
+ *
43
+ * When connectionId changes, the button will automatically update the existing
44
+ * connector instance with the new connection details. If you need to force a
45
+ * complete remount instead, set forceRemountOnConnectionChange to true.
46
+ */
30
47
  export const QuilttButton = <T extends ElementType = 'button'>({
31
48
  as,
32
49
  connectorId,
33
50
  connectionId,
34
51
  institution,
52
+ forceRemountOnConnectionChange = false,
35
53
  onEvent,
36
54
  onOpen,
37
55
  onLoad,
@@ -44,6 +62,46 @@ export const QuilttButton = <T extends ElementType = 'button'>({
44
62
  children,
45
63
  ...props
46
64
  }: QuilttButtonProps<T> & PropsOf<T>) => {
65
+ // Keep track of previous connectionId for change detection
66
+ const prevConnectionIdRef = useRef<string | undefined>(connectionId)
67
+ const prevCallbacksRef = useRef({
68
+ onEvent,
69
+ onOpen,
70
+ onLoad,
71
+ onExit,
72
+ onExitSuccess,
73
+ onExitAbort,
74
+ onExitError,
75
+ })
76
+
77
+ // Track if callbacks have changed to help with debugging
78
+ const currentCallbacks = {
79
+ onEvent,
80
+ onOpen,
81
+ onLoad,
82
+ onExit,
83
+ onExitSuccess,
84
+ onExitAbort,
85
+ onExitError,
86
+ }
87
+
88
+ const callbacksChanged = !isDeepEqual(prevCallbacksRef.current, currentCallbacks)
89
+
90
+ useEffect(() => {
91
+ prevCallbacksRef.current = currentCallbacks
92
+ })
93
+
94
+ // Warning for potential callback reference issues
95
+ useEffect(() => {
96
+ if (callbacksChanged && prevConnectionIdRef.current !== undefined) {
97
+ console.warn(
98
+ '[Quiltt] Callback functions changed after initial render. ' +
99
+ 'This may cause unexpected behavior. Consider memoizing callback functions ' +
100
+ 'with useCallback to maintain stable references.'
101
+ )
102
+ }
103
+ }, [callbacksChanged])
104
+
47
105
  const { open } = useQuilttConnector(connectorId, {
48
106
  connectionId,
49
107
  institution,
@@ -57,6 +115,11 @@ export const QuilttButton = <T extends ElementType = 'button'>({
57
115
  onExitError,
58
116
  })
59
117
 
118
+ // Update previous connectionId reference
119
+ useEffect(() => {
120
+ prevConnectionIdRef.current = connectionId
121
+ }, [connectionId])
122
+
60
123
  const Button = as || 'button'
61
124
 
62
125
  const handleClick = (event: MouseEvent<HTMLElement>) => {
@@ -70,8 +133,21 @@ export const QuilttButton = <T extends ElementType = 'button'>({
70
133
  open()
71
134
  }
72
135
 
136
+ // Generate key for forced remounting if enabled, but respect user-provided key
137
+ const buttonKey =
138
+ props.key ??
139
+ (forceRemountOnConnectionChange
140
+ ? `${connectorId}-${connectionId || 'no-connection'}`
141
+ : undefined)
142
+
73
143
  return (
74
- <Button onClick={handleClick} onLoad={onHtmlLoad} quiltt-connection={connectionId} {...props}>
144
+ <Button
145
+ key={buttonKey}
146
+ onClick={handleClick}
147
+ onLoad={onHtmlLoad}
148
+ quiltt-connection={connectionId}
149
+ {...props}
150
+ >
75
151
  {children}
76
152
  </Button>
77
153
  )
@@ -1,26 +1,40 @@
1
+ import { useEffect, useRef } from 'react'
1
2
  import type { ElementType, PropsWithChildren } from 'react'
2
3
 
3
4
  import type { ConnectorSDKCallbacks } from '@quiltt/core'
4
5
 
5
6
  import { useQuilttConnector } from '@/hooks/useQuilttConnector'
6
7
  import type { PropsOf } from '@/types'
8
+ import { isDeepEqual } from '@/utils/isDeepEqual'
7
9
 
8
10
  type QuilttContainerProps<T extends ElementType> = PropsWithChildren<
9
11
  {
10
12
  as?: T
11
13
  connectorId: string
12
14
  connectionId?: string // For Reconnect Mode
15
+
16
+ /**
17
+ * Forces complete remount when connectionId changes.
18
+ * Useful as a fallback for ensuring clean state.
19
+ * @default false
20
+ */
21
+ forceRemountOnConnectionChange?: boolean
13
22
  } & ConnectorSDKCallbacks
14
23
  >
15
24
 
16
25
  /**
17
26
  * QuilttContainer uses globally shared callbacks. It's recommended you only use
18
27
  * one Container at a time.
28
+ *
29
+ * When connectionId changes, the container will automatically update the existing
30
+ * connector instance with the new connection details. If you need to force a
31
+ * complete remount instead, set forceRemountOnConnectionChange to true.
19
32
  */
20
33
  export const QuilttContainer = <T extends ElementType = 'div'>({
21
34
  as,
22
35
  connectorId,
23
36
  connectionId,
37
+ forceRemountOnConnectionChange = false,
24
38
  onEvent,
25
39
  onLoad,
26
40
  onExit,
@@ -30,7 +44,46 @@ export const QuilttContainer = <T extends ElementType = 'div'>({
30
44
  children,
31
45
  ...props
32
46
  }: QuilttContainerProps<T> & PropsOf<T>) => {
47
+ // Keep track of previous connectionId for change detection
48
+ const prevConnectionIdRef = useRef<string | undefined>(connectionId)
49
+ const prevCallbacksRef = useRef({
50
+ onEvent,
51
+ onLoad,
52
+ onExit,
53
+ onExitSuccess,
54
+ onExitAbort,
55
+ onExitError,
56
+ })
57
+
58
+ // Track if callbacks have changed to help with debugging
59
+ const currentCallbacks = {
60
+ onEvent,
61
+ onLoad,
62
+ onExit,
63
+ onExitSuccess,
64
+ onExitAbort,
65
+ onExitError,
66
+ }
67
+
68
+ const callbacksChanged = !isDeepEqual(prevCallbacksRef.current, currentCallbacks)
69
+
70
+ useEffect(() => {
71
+ prevCallbacksRef.current = currentCallbacks
72
+ })
73
+
74
+ // Warning for potential callback reference issues
75
+ useEffect(() => {
76
+ if (callbacksChanged && prevConnectionIdRef.current !== undefined) {
77
+ console.warn(
78
+ '[Quiltt] Callback functions changed after initial render. ' +
79
+ 'This may cause unexpected behavior. Consider memoizing callback functions ' +
80
+ 'with useCallback to maintain stable references.'
81
+ )
82
+ }
83
+ }, [callbacksChanged])
84
+
33
85
  useQuilttConnector(connectorId, {
86
+ connectionId,
34
87
  nonce: props?.nonce, // Pass nonce for script loading if needed
35
88
  onEvent,
36
89
  onLoad,
@@ -40,10 +93,27 @@ export const QuilttContainer = <T extends ElementType = 'div'>({
40
93
  onExitError,
41
94
  })
42
95
 
96
+ // Update previous connectionId reference
97
+ useEffect(() => {
98
+ prevConnectionIdRef.current = connectionId
99
+ }, [connectionId])
100
+
43
101
  const Container = as || 'div'
44
102
 
103
+ // Generate key for forced remounting if enabled, but respect user-provided key
104
+ const containerKey =
105
+ props.key ??
106
+ (forceRemountOnConnectionChange
107
+ ? `${connectorId}-${connectionId || 'no-connection'}`
108
+ : undefined)
109
+
45
110
  return (
46
- <Container quiltt-container={connectorId} quiltt-connection={connectionId} {...props}>
111
+ <Container
112
+ key={containerKey}
113
+ quiltt-container={connectorId}
114
+ quiltt-connection={connectionId}
115
+ {...props}
116
+ >
47
117
  {children}
48
118
  </Container>
49
119
  )
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useCallback, useEffect, useState } from 'react'
3
+ import { useCallback, useEffect, useRef, useState } from 'react'
4
4
 
5
5
  import { cdnBase } from '@quiltt/core'
6
6
  import type {
@@ -30,8 +30,13 @@ export const useQuilttConnector = (
30
30
  const [connector, setConnector] = useState<ConnectorSDKConnector>()
31
31
  const [isOpening, setIsOpening] = useState<boolean>(false)
32
32
 
33
+ // Keep track of the previous connectionId to detect changes
34
+ const prevConnectionIdRef = useRef<string | undefined>(options?.connectionId)
35
+ const prevConnectorIdRef = useRef<string | undefined>(connectorId)
36
+ const connectorCreatedRef = useRef<boolean>(false)
37
+
33
38
  // Set Session
34
- // biome-ignore lint/correctness/useExhaustiveDependencies: We also need to update on status change
39
+ // biome-ignore lint/correctness/useExhaustiveDependencies: trigger effects when script status changes too
35
40
  useEffect(() => {
36
41
  if (typeof Quiltt === 'undefined') return
37
42
 
@@ -39,16 +44,34 @@ export const useQuilttConnector = (
39
44
  }, [status, session?.token])
40
45
 
41
46
  // Set Connector
42
- // biome-ignore lint/correctness/useExhaustiveDependencies: We also need to update on status change
47
+ // biome-ignore lint/correctness/useExhaustiveDependencies: trigger effects when script status changes too
43
48
  useEffect(() => {
44
49
  if (typeof Quiltt === 'undefined' || !connectorId) return
45
50
 
46
- if (options?.connectionId) {
47
- setConnector(Quiltt.reconnect(connectorId, { connectionId: options.connectionId }))
48
- } else {
49
- setConnector(Quiltt.connect(connectorId, { institution: options?.institution }))
51
+ const currentConnectionId = options?.connectionId
52
+ const currentInstitution = options?.institution
53
+
54
+ // Check for changes
55
+ const connectionIdChanged = prevConnectionIdRef.current !== currentConnectionId
56
+ const connectorIdChanged = prevConnectorIdRef.current !== connectorId
57
+ const hasChanges = connectionIdChanged || connectorIdChanged || !connectorCreatedRef.current
58
+
59
+ // Update if there are changes, regardless of what the changes are
60
+ if (hasChanges) {
61
+ if (currentConnectionId) {
62
+ // Always use reconnect when connectionId is available
63
+ setConnector(Quiltt.reconnect(connectorId, { connectionId: currentConnectionId }))
64
+ } else {
65
+ // Use connect for new connections without connectionId
66
+ setConnector(Quiltt.connect(connectorId, { institution: currentInstitution }))
67
+ }
68
+
69
+ // Update refs
70
+ connectorCreatedRef.current = true
71
+ prevConnectionIdRef.current = currentConnectionId
72
+ prevConnectorIdRef.current = connectorId
50
73
  }
51
- }, [status, connectorId, options?.connectionId, options?.institution])
74
+ }, [connectorId, options?.connectionId, options?.institution, status])
52
75
 
53
76
  // onEvent
54
77
  useEffect(() => {