@quiltt/react 3.9.0 → 3.9.2

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.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#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.
8
+
9
+ - [#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
10
+
11
+ - [#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
12
+
13
+ - 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)]:
14
+ - @quiltt/core@3.9.2
15
+
16
+ ## 3.9.1
17
+
18
+ ### Patch Changes
19
+
20
+ - [#310](https://github.com/quiltt/quiltt-js/pull/310) [`86b39ac`](https://github.com/quiltt/quiltt-js/commit/86b39ac7015fcf21d1c1962df1a76f84c2af9801) Thanks [@rubendinho](https://github.com/rubendinho)! - Added explicit file extensions to import statements to comply with strict ESM module resolution.
21
+
22
+ - Updated dependencies [[`86b39ac`](https://github.com/quiltt/quiltt-js/commit/86b39ac7015fcf21d1c1962df1a76f84c2af9801)]:
23
+ - @quiltt/core@3.9.1
24
+
3
25
  ## 3.9.0
4
26
 
5
27
  ### Minor 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
- import '@apollo/client/react/hooks';
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
- import { ApolloProvider } from '@apollo/client/react';
9
- import { u as useQuilttSession } from './useQuilttSession-client-C0lMwHkc.js';
8
+ import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.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,
@@ -2,7 +2,7 @@
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { useState } from 'react';
4
4
  import '@quiltt/core';
5
- import '@apollo/client/react/hooks';
5
+ import '@apollo/client/react/hooks/useApolloClient.js';
6
6
  import { Q as QuilttSettings } from './useQuilttSettings-client-DU_Qfc8X.js';
7
7
  import './useSession-client-CG5lGS9F.js';
8
8
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
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';
5
- import { useApolloClient } from '@apollo/client/react/hooks';
4
+ import { RefObject, useLayoutEffect, Dispatch, SetStateAction, FC, PropsWithChildren, Component, ComponentType, MouseEvent } from 'react';
5
+ import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient.js';
6
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
7
7
 
8
8
  declare function useEventListener<K extends keyof MediaQueryListEventMap>(eventName: K, handler: (event: MediaQueryListEventMap[K]) => void, element: RefObject<MediaQueryList>, options?: boolean | AddEventListenerOptions): void;
@@ -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
 
@@ -136,13 +135,19 @@ declare const QuilttSettingsProvider: FC<QuilttSettingsProviderProps>;
136
135
  type AnyTag = string | FC<any> | (new (props: any) => Component);
137
136
  type PropsOf<Tag> = Tag extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[Tag] : Tag extends ComponentType<infer Props> ? Props & JSX.IntrinsicAttributes : never;
138
137
 
139
- type QuilttButtonProps<T extends AnyTag> = PropsWithChildren<{
138
+ type BaseQuilttButtonProps<T extends AnyTag> = {
140
139
  as?: T;
141
140
  connectorId: string;
142
141
  connectionId?: string;
143
142
  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;
143
+ onClick?: (event: MouseEvent<HTMLElement>) => void;
144
+ };
145
+ type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
146
+ onLoad?: ConnectorSDKCallbacks['onLoad'];
147
+ onHtmlLoad?: React.ReactEventHandler<HTMLElement>;
148
+ };
149
+ type QuilttButtonProps<T extends AnyTag> = PropsWithChildren<BaseQuilttButtonProps<T> & QuilttCallbackProps>;
150
+ declare const QuilttButton: <T extends AnyTag = "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
151
 
147
152
  type QuilttContainerProps<T extends AnyTag> = PropsWithChildren<{
148
153
  as?: T;
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
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-pXeKB9BO.js';
5
- export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-pXeKB9BO.js';
6
- export { u as useQuilttClient } from './useQuilttClient-client-vI5KrJv2.js';
7
- import { u as useQuilttConnector } from './useQuilttConnector-client-D1pfKDpI.js';
8
- export { u as useQuilttSession } from './useQuilttSession-client-C0lMwHkc.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
+ export { u as useQuilttClient } from './useQuilttClient-client-CAAUait1.js';
7
+ import { u as useQuilttConnector } from './useQuilttConnector-client-BPcRwcNX.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';
12
12
  import { jsx } from 'react/jsx-runtime';
13
- import { Q as QuilttSettingsProvider } from './QuilttSettingsProvider-client-DK_eTIy6.js';
13
+ import { Q as QuilttSettingsProvider } from './QuilttSettingsProvider-client-EfyHgg4i.js';
14
14
 
15
15
  const QuilttProvider = ({ clientId, token, children })=>{
16
16
  return /*#__PURE__*/ jsx(QuilttSettingsProvider, {
@@ -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,5 +1,5 @@
1
1
  'use client';
2
- import { useApolloClient } from '@apollo/client/react/hooks';
2
+ import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient.js';
3
3
 
4
4
  const useQuilttClient = useApolloClient;
5
5
 
@@ -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-C0lMwHkc.js';
4
+ import { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
5
5
  import { u as useScript } from './useScript-client-Cx5nb9RW.js';
6
6
 
7
- var version = "3.9.0";
7
+ var version = "3.9.2";
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-pXeKB9BO.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
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quiltt/react",
3
- "version": "3.9.0",
4
- "description": "React components and hooks for Quiltt Connector",
3
+ "version": "3.9.2",
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,9 +32,10 @@
34
32
  "src/**",
35
33
  "CHANGELOG.md"
36
34
  ],
35
+ "main": "dist/index.js",
37
36
  "dependencies": {
38
37
  "@apollo/client": "^3.11.8",
39
- "@quiltt/core": "3.9.0"
38
+ "@quiltt/core": "3.9.2"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@biomejs/biome": "1.9.4",
@@ -47,12 +46,16 @@
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.2"
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,17 +1,30 @@
1
- import type { PropsWithChildren } from 'react'
1
+ import type { MouseEvent, PropsWithChildren } from 'react'
2
2
 
3
3
  import type { ConnectorSDKCallbacks } from '@quiltt/core'
4
4
 
5
5
  import { useQuilttConnector } from '../hooks/useQuilttConnector'
6
6
  import type { AnyTag, PropsOf } from '../types'
7
7
 
8
+ // Base button props without callback-specific properties
9
+ type BaseQuilttButtonProps<T extends AnyTag> = {
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
8
26
  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
27
+ BaseQuilttButtonProps<T> & QuilttCallbackProps
15
28
  >
16
29
 
17
30
  export const QuilttButton = <T extends AnyTag = 'button'>({
@@ -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,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useApolloClient } from '@apollo/client/react/hooks'
3
+ import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient.js'
4
4
 
5
5
  export const useQuilttClient = useApolloClient
6
6
 
@@ -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
-
6
- import { ApolloProvider } from '@apollo/client/react'
7
5
 
6
+ import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.js'
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
  }
@@ -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
+ }