@quiltt/react 3.9.1 → 3.9.3

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,27 @@
1
1
  # @quiltt/react
2
2
 
3
+ ## 3.9.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#319](https://github.com/quiltt/quiltt-js/pull/319) [`b97a814`](https://github.com/quiltt/quiltt-js/commit/b97a814b87b8bfec9f8b4bb155e1140724e441eb) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Improve typings for query client and react components
8
+
9
+ - Updated dependencies [[`b97a814`](https://github.com/quiltt/quiltt-js/commit/b97a814b87b8bfec9f8b4bb155e1140724e441eb)]:
10
+ - @quiltt/core@3.9.3
11
+
12
+ ## 3.9.2
13
+
14
+ ### Patch Changes
15
+
16
+ - [#316](https://github.com/quiltt/quiltt-js/pull/316) [`de5d43e`](https://github.com/quiltt/quiltt-js/commit/de5d43e664a8bbb04595816718f5c645a9c3df27) Thanks [@rubendinho](https://github.com/rubendinho)! - Updated `main` param to `package.json` to improve analyzing the package.
17
+
18
+ - [#313](https://github.com/quiltt/quiltt-js/pull/313) [`3b789c9`](https://github.com/quiltt/quiltt-js/commit/3b789c9413ab2f9bdda965248ed7a8ccaf270172) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Update typings for QuilttButton onLoad handler
19
+
20
+ - [#312](https://github.com/quiltt/quiltt-js/pull/312) [`11ba6a3`](https://github.com/quiltt/quiltt-js/commit/11ba6a3af1975349a63640bb99ed0e34ffee3f1c) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Update session handling in QuilttAuthProvider
21
+
22
+ - Updated dependencies [[`de5d43e`](https://github.com/quiltt/quiltt-js/commit/de5d43e664a8bbb04595816718f5c645a9c3df27), [`3b789c9`](https://github.com/quiltt/quiltt-js/commit/3b789c9413ab2f9bdda965248ed7a8ccaf270172), [`11ba6a3`](https://github.com/quiltt/quiltt-js/commit/11ba6a3af1975349a63640bb99ed0e34ffee3f1c)]:
23
+ - @quiltt/core@3.9.2
24
+
3
25
  ## 3.9.1
4
26
 
5
27
  ### Patch Changes
package/README.md CHANGED
@@ -9,17 +9,27 @@ See the guides [here](https://www.quiltt.dev/connector/sdks/react).
9
9
 
10
10
  ## Installation
11
11
 
12
+ With `npm`:
13
+
12
14
  ```shell
13
15
  $ npm install @quiltt/react
14
- # or
16
+ ```
17
+
18
+ With `yarn`:
19
+
20
+ ```shell
15
21
  $ yarn add @quiltt/react
16
- # or
22
+ ```
23
+
24
+ With `pnpm`:
25
+
26
+ ```shell
17
27
  $ pnpm add @quiltt/react
18
28
  ```
19
29
 
20
30
  ## Core Modules and Types
21
31
 
22
- The `@quiltt/react` library ships with `@quiltt/core`, which provides an Auth API and essential functionality for building Javascript-based applications with Quiltt. See the [Core README](../core/README.md) for more information.
32
+ The `@quiltt/react` library ships with `@quiltt/core`, which provides an API clients and essential functionality for building Javascript-based applications with Quiltt. See the [Core README](../core/README.md) for more information.
23
33
 
24
34
  ## React Components
25
35
 
@@ -38,10 +48,11 @@ By default, the rendered component will be a `<button>` element, but you can sup
38
48
  ```tsx
39
49
  import { useState } from 'react'
40
50
  import { QuilttButton } from '@quiltt/react'
51
+ import type { ConnectorSDKCallbackMetadata } from '@quiltt/react'
41
52
 
42
53
  export const App = () => {
43
54
  const [connectionId, setConnectionId] = useState<string>()
44
- const handleExitSuccess = (metadata) => {
55
+ const handleExitSuccess = (metadata: ConnectorSDKCallbackMetadata) => {
45
56
  setConnectionId(metadata?.connectionId)
46
57
  }
47
58
 
@@ -71,10 +82,11 @@ By default, the rendered component will be a `<div>` element, but you can supply
71
82
  ```tsx
72
83
  import { useState } from 'react'
73
84
  import { QuilttContainer } from '@quiltt/react'
85
+ import type { ConnectorSDKCallbackMetadata } from '@quiltt/react'
74
86
 
75
87
  export const App = () => {
76
88
  const [connectionId, setConnectionId] = useState<string>()
77
- const handleExitSuccess = (metadata) => {
89
+ const handleExitSuccess = (metadata: ConnectorSDKCallbackMetadata) => {
78
90
  setConnectionId(metadata?.connectionId)
79
91
  }
80
92
 
@@ -184,7 +196,7 @@ export default App
184
196
 
185
197
  ## Typescript support
186
198
 
187
- `@quiltt/react` is written in Typescript and ships with its own type definitions. It also ships with the type definitions from `@quiltt/core`.
199
+ `@quiltt/react` is written in Typescript and ships with its own type definitions, as well as the type definitions from `@quiltt/core`.
188
200
 
189
201
  ## License
190
202
 
@@ -1,12 +1,12 @@
1
1
  'use client';
2
- import { useCallback, useMemo, useEffect } from 'react';
2
+ import { useCallback, useRef, useMemo, useEffect } from 'react';
3
3
  import { JsonWebTokenParse, QuilttClient, InMemoryCache } from '@quiltt/core';
4
4
  import '@apollo/client/react/hooks/useApolloClient.js';
5
5
  import './useQuilttSettings-client-DU_Qfc8X.js';
6
6
  import './useSession-client-CG5lGS9F.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-DA9F_3KG.js';
9
+ import { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
10
10
 
11
11
  const useIdentifySession = (auth, setSession)=>{
12
12
  const identifySession = useCallback(async (payload, callbacks)=>{
@@ -102,14 +102,64 @@ const useRevokeSession = (auth, session, setSession)=>{
102
102
  return revokeSession;
103
103
  };
104
104
 
105
+ /**
106
+ * Performs a deep equality comparison between two values
107
+ *
108
+ * This function recursively compares all properties to determine if they are equal.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }) // true
113
+ * isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } }) // false
114
+ * ```
115
+ */ const isDeepEqual = (obj1, obj2)=>{
116
+ // Handle primitive types and null/undefined
117
+ if (obj1 === obj2) return true;
118
+ if (obj1 === null || obj2 === null || typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
119
+ // Handle special object types
120
+ if (obj1 instanceof Date && obj2 instanceof Date) {
121
+ return obj1.getTime() === obj2.getTime();
122
+ }
123
+ if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
124
+ return obj1.toString() === obj2.toString();
125
+ }
126
+ if (obj1 instanceof Map && obj2 instanceof Map) {
127
+ if (obj1.size !== obj2.size) return false;
128
+ for (const [key, value] of obj1){
129
+ if (!obj2.has(key) || !isDeepEqual(value, obj2.get(key))) return false;
130
+ }
131
+ return true;
132
+ }
133
+ if (obj1 instanceof Set && obj2 instanceof Set) {
134
+ if (obj1.size !== obj2.size) return false;
135
+ for (const item of obj1){
136
+ if (!Array.from(obj2).some((value)=>isDeepEqual(item, value))) return false;
137
+ }
138
+ return true;
139
+ }
140
+ // Handle arrays
141
+ if (Array.isArray(obj1) && Array.isArray(obj2)) {
142
+ if (obj1.length !== obj2.length) return false;
143
+ return obj1.every((value, index)=>isDeepEqual(value, obj2[index]));
144
+ }
145
+ // If one is array and other isn't, they're not equal
146
+ if (Array.isArray(obj1) || Array.isArray(obj2)) return false;
147
+ const keys1 = Object.keys(obj1);
148
+ const keys2 = Object.keys(obj2);
149
+ if (keys1.length !== keys2.length) return false;
150
+ return keys1.every((key)=>{
151
+ return Object.prototype.hasOwnProperty.call(obj2, key) && isDeepEqual(obj1[key], obj2[key]);
152
+ });
153
+ };
154
+
105
155
  /**
106
156
  * If a token is provided, will validate the token against the api and then import
107
157
  * it into trusted storage. While this process is happening, the component is put
108
158
  * into a loading state and the children are not rendered to prevent race conditions
109
159
  * from triggering within the transitionary state.
110
- *
111
160
  */ const QuilttAuthProvider = ({ token, children })=>{
112
161
  const { session, importSession } = useQuilttSession();
162
+ const previousSessionRef = useRef(session);
113
163
  // @todo: extract into a provider so it can accessed by child components
114
164
  const graphQLClient = useMemo(()=>new QuilttClient({
115
165
  cache: new InMemoryCache()
@@ -121,12 +171,15 @@ const useRevokeSession = (auth, session, setSession)=>{
121
171
  token,
122
172
  importSession
123
173
  ]);
124
- // Reset Client Store when logging in or out
125
- // biome-ignore lint/correctness/useExhaustiveDependencies: We should reset the store whenever the session changes
174
+ // Reset Client Store when session changes (using deep comparison)
126
175
  useEffect(()=>{
127
- graphQLClient.resetStore();
176
+ if (!isDeepEqual(session, previousSessionRef.current)) {
177
+ graphQLClient.resetStore();
178
+ previousSessionRef.current = session;
179
+ }
128
180
  }, [
129
- session
181
+ session,
182
+ graphQLClient
130
183
  ]);
131
184
  return /*#__PURE__*/ jsx(ApolloProvider, {
132
185
  client: graphQLClient,
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Maybe, QuilttJWT, UsernamePayload, UnprocessableData, AuthAPI, PasscodePayload, ConnectorSDKConnectorOptions, ConnectorSDKCallbacks } from '@quiltt/core';
2
2
  export * from '@quiltt/core';
3
3
  import * as react from 'react';
4
- import { RefObject, useLayoutEffect, Dispatch, SetStateAction, FC, PropsWithChildren, Component, ComponentType } from 'react';
4
+ import { RefObject, useLayoutEffect, Dispatch, SetStateAction, FC, PropsWithChildren, JSX, ComponentType, ElementType, MouseEvent } from 'react';
5
5
  import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient.js';
6
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
7
7
 
@@ -115,7 +115,6 @@ type QuilttAuthProviderProps = PropsWithChildren & {
115
115
  * it into trusted storage. While this process is happening, the component is put
116
116
  * into a loading state and the children are not rendered to prevent race conditions
117
117
  * from triggering within the transitionary state.
118
- *
119
118
  */
120
119
  declare const QuilttAuthProvider: FC<QuilttAuthProviderProps>;
121
120
 
@@ -133,18 +132,23 @@ type QuilttSettingsProviderProps = PropsWithChildren & {
133
132
  };
134
133
  declare const QuilttSettingsProvider: FC<QuilttSettingsProviderProps>;
135
134
 
136
- type AnyTag = string | FC<any> | (new (props: any) => Component);
137
135
  type PropsOf<Tag> = Tag extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[Tag] : Tag extends ComponentType<infer Props> ? Props & JSX.IntrinsicAttributes : never;
138
136
 
139
- type QuilttButtonProps<T extends AnyTag> = PropsWithChildren<{
137
+ type BaseQuilttButtonProps<T extends ElementType> = {
140
138
  as?: T;
141
139
  connectorId: string;
142
140
  connectionId?: string;
143
141
  institution?: string;
144
- } & ConnectorSDKCallbacks>;
145
- declare const QuilttButton: <T extends AnyTag = "button">({ as, connectorId, connectionId, institution, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props }: QuilttButtonProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
142
+ onClick?: (event: MouseEvent<HTMLElement>) => void;
143
+ };
144
+ type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
145
+ onLoad?: ConnectorSDKCallbacks['onLoad'];
146
+ onHtmlLoad?: React.ReactEventHandler<HTMLElement>;
147
+ };
148
+ type QuilttButtonProps<T extends ElementType> = PropsWithChildren<BaseQuilttButtonProps<T> & QuilttCallbackProps>;
149
+ 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;
146
150
 
147
- type QuilttContainerProps<T extends AnyTag> = PropsWithChildren<{
151
+ type QuilttContainerProps<T extends ElementType> = PropsWithChildren<{
148
152
  as?: T;
149
153
  connectorId: string;
150
154
  connectionId?: string;
@@ -153,6 +157,6 @@ type QuilttContainerProps<T extends AnyTag> = PropsWithChildren<{
153
157
  * QuilttContainer uses globally shared callbacks. It's recommended you only use
154
158
  * one Container at a time.
155
159
  */
156
- declare const QuilttContainer: <T extends AnyTag = "div">({ as, connectorId, connectionId, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props }: QuilttContainerProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
160
+ 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;
157
161
 
158
162
  export { type AuthenticateSession, type IdentifySession, type ImportSession, QuilttAuthProvider, QuilttButton, QuilttContainer, QuilttProvider, QuilttSettings, QuilttSettingsProvider, type RevokeSession, type SetSession, type UseQuilttSession, useAuthenticateSession, useEventListener, useIdentifySession, useImportSession, useIsomorphicLayoutEffect, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useRevokeSession, useSession, useStorage };
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
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-CqVLC1w4.js';
5
- export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-CqVLC1w4.js';
4
+ import { Q as QuilttAuthProvider } from './QuilttAuthProvider-client-D6Iok6TS.js';
5
+ export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-D6Iok6TS.js';
6
6
  export { u as useQuilttClient } from './useQuilttClient-client-CAAUait1.js';
7
- import { u as useQuilttConnector } from './useQuilttConnector-client-L80c55xy.js';
8
- export { u as useQuilttSession } from './useQuilttSession-client-DA9F_3KG.js';
7
+ import { u as useQuilttConnector } from './useQuilttConnector-client-720xcXap.js';
8
+ export { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
9
9
  export { Q as QuilttSettings, u as useQuilttSettings } from './useQuilttSettings-client-DU_Qfc8X.js';
10
10
  export { u as useSession } from './useSession-client-CG5lGS9F.js';
11
11
  export { u as useStorage } from './useStorage-client-B3keU-oI.js';
@@ -22,7 +22,7 @@ const QuilttProvider = ({ clientId, token, children })=>{
22
22
  });
23
23
  };
24
24
 
25
- const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props })=>{
25
+ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props })=>{
26
26
  const { open } = useQuilttConnector(connectorId, {
27
27
  connectionId,
28
28
  institution,
@@ -35,8 +35,18 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
35
35
  onExitError
36
36
  });
37
37
  const Button = as || 'button';
38
+ const handleClick = (event)=>{
39
+ // Call the user's onClick handler first to allow for:
40
+ // 1. Pre-open validation
41
+ // 2. Preventing opening via event.preventDefault()
42
+ // 3. Setting up state before connector opens
43
+ if (onClick) onClick(event);
44
+ // Then open the Quiltt connector
45
+ open();
46
+ };
38
47
  return /*#__PURE__*/ jsx(Button, {
39
- onClick: open,
48
+ onClick: handleClick,
49
+ onLoad: onHtmlLoad,
40
50
  "quiltt-connection": connectionId,
41
51
  ...props,
42
52
  children: children
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import { useState, useEffect, useCallback } from 'react';
3
3
  import { cdnBase } from '@quiltt/core';
4
- import { u as useQuilttSession } from './useQuilttSession-client-DA9F_3KG.js';
5
- import { u as useScript } from './useScript-client-Cx5nb9RW.js';
4
+ import { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
5
+ import { u as useScript } from './useScript-client-CWRBlZBC.js';
6
6
 
7
- var version = "3.9.1";
7
+ var version = "3.9.3";
8
8
 
9
9
  const useQuilttConnector = (connectorId, options)=>{
10
10
  const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`);
@@ -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-CqVLC1w4.js';
4
+ import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-client-D6Iok6TS.js';
5
5
  import { u as useQuilttSettings } from './useQuilttSettings-client-DU_Qfc8X.js';
6
6
  import { u as useSession } from './useSession-client-CG5lGS9F.js';
7
7
 
@@ -73,9 +73,6 @@ function useScript(src, options) {
73
73
  scriptNode.removeEventListener('load', setStateFromEvent);
74
74
  scriptNode.removeEventListener('error', setStateFromEvent);
75
75
  }
76
- if (scriptNode && options?.removeOnUnmount) {
77
- scriptNode.remove();
78
- }
79
76
  };
80
77
  }, [
81
78
  src,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quiltt/react",
3
- "version": "3.9.1",
4
- "description": "React components and hooks for Quiltt Connector",
3
+ "version": "3.9.3",
4
+ "description": "React Components and Hooks for Quiltt Connector",
5
5
  "keywords": [
6
6
  "quiltt",
7
7
  "quiltt-connector",
@@ -15,12 +15,10 @@
15
15
  "directory": "packages/react"
16
16
  },
17
17
  "license": "MIT",
18
- "sideEffects": [
19
- "./src/hooks/useQuilttConnector.ts",
20
- "./src/hooks/useQuilttSettings.ts",
21
- "./src/hooks/useSession.ts",
22
- "./src/hooks/useStorage.ts"
23
- ],
18
+ "bugs": {
19
+ "url": "https://github.com/quiltt/quiltt-js/issues"
20
+ },
21
+ "sideEffects": [],
24
22
  "type": "module",
25
23
  "exports": {
26
24
  ".": {
@@ -34,25 +32,30 @@
34
32
  "src/**",
35
33
  "CHANGELOG.md"
36
34
  ],
35
+ "main": "dist/index.js",
37
36
  "dependencies": {
38
- "@apollo/client": "^3.11.8",
39
- "@quiltt/core": "3.9.1"
37
+ "@apollo/client": "^3.12.4",
38
+ "@quiltt/core": "3.9.3"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@biomejs/biome": "1.9.4",
43
- "@types/node": "22.10.2",
42
+ "@types/node": "22.10.5",
44
43
  "@types/react": "18.3.12",
45
44
  "@types/react-dom": "18.3.1",
46
- "bunchee": "5.6.1",
45
+ "bunchee": "6.2.0",
47
46
  "react": "18.3.1",
48
47
  "react-dom": "18.3.1",
49
48
  "rimraf": "6.0.1",
50
- "typescript": "5.6.3"
49
+ "typescript": "5.7.3"
51
50
  },
52
51
  "peerDependencies": {
53
52
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
54
53
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
55
54
  },
55
+ "tags": [
56
+ "quiltt",
57
+ "react"
58
+ ],
56
59
  "publishConfig": {
57
60
  "access": "public"
58
61
  },
@@ -1,20 +1,33 @@
1
- import type { PropsWithChildren } from 'react'
1
+ import type { ElementType, MouseEvent, PropsWithChildren } from 'react'
2
2
 
3
3
  import type { ConnectorSDKCallbacks } from '@quiltt/core'
4
4
 
5
5
  import { useQuilttConnector } from '../hooks/useQuilttConnector'
6
- import type { AnyTag, PropsOf } from '../types'
7
-
8
- type QuilttButtonProps<T extends AnyTag> = PropsWithChildren<
9
- {
10
- as?: T
11
- connectorId: string
12
- connectionId?: string // For Reconnect Mode
13
- institution?: string // For Connect Mode
14
- } & ConnectorSDKCallbacks
6
+ import type { PropsOf } from '../types'
7
+
8
+ // Base button props without callback-specific properties
9
+ type BaseQuilttButtonProps<T extends ElementType> = {
10
+ as?: T
11
+ connectorId: string
12
+ connectionId?: string // For Reconnect Mode
13
+ institution?: string // For Connect Mode
14
+ // Override the native onClick handler
15
+ onClick?: (event: MouseEvent<HTMLElement>) => void
16
+ }
17
+
18
+ // Separate SDK callback types
19
+ type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
20
+ // Separate the SDK onLoad from the HTML onLoad to avoid conflicts
21
+ onLoad?: ConnectorSDKCallbacks['onLoad'] // Handles SDK initialization
22
+ onHtmlLoad?: React.ReactEventHandler<HTMLElement> // Handles DOM element load
23
+ }
24
+
25
+ // Combined type for the full component
26
+ type QuilttButtonProps<T extends ElementType> = PropsWithChildren<
27
+ BaseQuilttButtonProps<T> & QuilttCallbackProps
15
28
  >
16
29
 
17
- export const QuilttButton = <T extends AnyTag = 'button'>({
30
+ export const QuilttButton = <T extends ElementType = 'button'>({
18
31
  as,
19
32
  connectorId,
20
33
  connectionId,
@@ -26,6 +39,8 @@ export const QuilttButton = <T extends AnyTag = 'button'>({
26
39
  onExitSuccess,
27
40
  onExitAbort,
28
41
  onExitError,
42
+ onClick,
43
+ onHtmlLoad,
29
44
  children,
30
45
  ...props
31
46
  }: QuilttButtonProps<T> & PropsOf<T>) => {
@@ -43,8 +58,19 @@ export const QuilttButton = <T extends AnyTag = 'button'>({
43
58
 
44
59
  const Button = as || 'button'
45
60
 
61
+ const handleClick = (event: MouseEvent<HTMLElement>) => {
62
+ // Call the user's onClick handler first to allow for:
63
+ // 1. Pre-open validation
64
+ // 2. Preventing opening via event.preventDefault()
65
+ // 3. Setting up state before connector opens
66
+ if (onClick) onClick(event)
67
+
68
+ // Then open the Quiltt connector
69
+ open()
70
+ }
71
+
46
72
  return (
47
- <Button onClick={open} quiltt-connection={connectionId} {...props}>
73
+ <Button onClick={handleClick} onLoad={onHtmlLoad} quiltt-connection={connectionId} {...props}>
48
74
  {children}
49
75
  </Button>
50
76
  )
@@ -1,11 +1,11 @@
1
- import type { PropsWithChildren } from 'react'
1
+ import type { ElementType, PropsWithChildren } from 'react'
2
2
 
3
3
  import type { ConnectorSDKCallbacks } from '@quiltt/core'
4
4
 
5
5
  import { useQuilttConnector } from '../hooks/useQuilttConnector'
6
- import type { AnyTag, PropsOf } from '../types'
6
+ import type { PropsOf } from '../types'
7
7
 
8
- type QuilttContainerProps<T extends AnyTag> = PropsWithChildren<
8
+ type QuilttContainerProps<T extends ElementType> = PropsWithChildren<
9
9
  {
10
10
  as?: T
11
11
  connectorId: string
@@ -17,7 +17,7 @@ type QuilttContainerProps<T extends AnyTag> = PropsWithChildren<
17
17
  * QuilttContainer uses globally shared callbacks. It's recommended you only use
18
18
  * one Container at a time.
19
19
  */
20
- export const QuilttContainer = <T extends AnyTag = 'div'>({
20
+ export const QuilttContainer = <T extends ElementType = 'div'>({
21
21
  as,
22
22
  connectorId,
23
23
  connectionId,
@@ -1,13 +1,13 @@
1
1
  'use client'
2
2
 
3
+ import { useEffect, useMemo, useRef } from 'react'
3
4
  import type { FC, PropsWithChildren } from 'react'
4
- import { useEffect, useMemo } from 'react'
5
5
 
6
6
  import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.js'
7
-
8
7
  import { InMemoryCache, QuilttClient } from '@quiltt/core'
9
8
 
10
- import { useQuilttSession } from '../hooks'
9
+ import { useQuilttSession } from '@/hooks'
10
+ import { isDeepEqual } from '@/utils'
11
11
 
12
12
  type QuilttAuthProviderProps = PropsWithChildren & {
13
13
  /** The Session token obtained from the server */
@@ -19,10 +19,10 @@ type QuilttAuthProviderProps = PropsWithChildren & {
19
19
  * it into trusted storage. While this process is happening, the component is put
20
20
  * into a loading state and the children are not rendered to prevent race conditions
21
21
  * from triggering within the transitionary state.
22
- *
23
22
  */
24
23
  export const QuilttAuthProvider: FC<QuilttAuthProviderProps> = ({ token, children }) => {
25
24
  const { session, importSession } = useQuilttSession()
25
+ const previousSessionRef = useRef(session)
26
26
 
27
27
  // @todo: extract into a provider so it can accessed by child components
28
28
  const graphQLClient = useMemo(
@@ -38,11 +38,13 @@ export const QuilttAuthProvider: FC<QuilttAuthProviderProps> = ({ token, childre
38
38
  if (token) importSession(token)
39
39
  }, [token, importSession])
40
40
 
41
- // Reset Client Store when logging in or out
42
- // biome-ignore lint/correctness/useExhaustiveDependencies: We should reset the store whenever the session changes
41
+ // Reset Client Store when session changes (using deep comparison)
43
42
  useEffect(() => {
44
- graphQLClient.resetStore()
45
- }, [session])
43
+ if (!isDeepEqual(session, previousSessionRef.current)) {
44
+ graphQLClient.resetStore()
45
+ previousSessionRef.current = session
46
+ }
47
+ }, [session, graphQLClient])
46
48
 
47
49
  return <ApolloProvider client={graphQLClient}>{children}</ApolloProvider>
48
50
  }
package/src/types.ts CHANGED
@@ -1,6 +1,4 @@
1
- import type { Component, ComponentType, FC } from 'react'
2
-
3
- export type AnyTag = string | FC<any> | (new (props: any) => Component)
1
+ import type { ComponentType, JSX } from 'react'
4
2
 
5
3
  export type PropsOf<Tag> = Tag extends keyof JSX.IntrinsicElements
6
4
  ? JSX.IntrinsicElements[Tag]
@@ -0,0 +1 @@
1
+ export * from './isDeepEqual'
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Performs a deep equality comparison between two values
3
+ *
4
+ * This function recursively compares all properties to determine if they are equal.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }) // true
9
+ * isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } }) // false
10
+ * ```
11
+ */
12
+ export const isDeepEqual = (obj1: unknown, obj2: unknown): boolean => {
13
+ // Handle primitive types and null/undefined
14
+ if (obj1 === obj2) return true
15
+ if (obj1 === null || obj2 === null || typeof obj1 !== 'object' || typeof obj2 !== 'object')
16
+ return false
17
+
18
+ // Handle special object types
19
+ if (obj1 instanceof Date && obj2 instanceof Date) {
20
+ return obj1.getTime() === obj2.getTime()
21
+ }
22
+ if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
23
+ return obj1.toString() === obj2.toString()
24
+ }
25
+ if (obj1 instanceof Map && obj2 instanceof Map) {
26
+ if (obj1.size !== obj2.size) return false
27
+ for (const [key, value] of obj1) {
28
+ if (!obj2.has(key) || !isDeepEqual(value, obj2.get(key))) return false
29
+ }
30
+ return true
31
+ }
32
+ if (obj1 instanceof Set && obj2 instanceof Set) {
33
+ if (obj1.size !== obj2.size) return false
34
+ for (const item of obj1) {
35
+ if (!Array.from(obj2).some((value) => isDeepEqual(item, value))) return false
36
+ }
37
+ return true
38
+ }
39
+
40
+ // Handle arrays
41
+ if (Array.isArray(obj1) && Array.isArray(obj2)) {
42
+ if (obj1.length !== obj2.length) return false
43
+ return obj1.every((value, index) => isDeepEqual(value, obj2[index]))
44
+ }
45
+
46
+ // If one is array and other isn't, they're not equal
47
+ if (Array.isArray(obj1) || Array.isArray(obj2)) return false
48
+
49
+ const keys1 = Object.keys(obj1)
50
+ const keys2 = Object.keys(obj2)
51
+
52
+ if (keys1.length !== keys2.length) return false
53
+
54
+ return keys1.every((key) => {
55
+ return (
56
+ Object.prototype.hasOwnProperty.call(obj2, key) &&
57
+ isDeepEqual((obj1 as any)[key], (obj2 as any)[key])
58
+ )
59
+ })
60
+ }