@stytch/nextjs 18.0.0 → 20.0.0

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,24 @@
1
1
  # @stytch/nextjs
2
2
 
3
+ ## 20.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [cbab0c30]
8
+ - @stytch/vanilla-js@4.12.0
9
+
10
+ ## 19.0.0
11
+
12
+ ### Major Changes
13
+
14
+ - a0fbe9f: Update minimum peer dependency on `@stytch/vanilla-js` to `^4.9.0`
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [a0fbe9f]
19
+ - Updated dependencies [a0fbe9f]
20
+ - @stytch/vanilla-js@4.9.0
21
+
3
22
  ## 18.0.0
4
23
 
5
24
  ### Patch Changes
@@ -1,5 +1,56 @@
1
1
  import React, { useRef, useState, useEffect, useCallback, createContext, useContext, useMemo, useLayoutEffect } from 'react';
2
2
 
3
+ const createDeepEqual = ({ KEYS_TO_EXCLUDE = [] } = {}) => {
4
+ // If comparing functions, this may need some work. Not sure the
5
+ // best path for this: compare instance (what it currently does),
6
+ // stringify and compare, etc.
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ const deepEqual = (a, b) => {
9
+ // Ensures type is the same
10
+ if (typeof a !== typeof b)
11
+ return false;
12
+ // arrays, null, and objects all have type 'object'
13
+ if (a === null || b === null)
14
+ return a === b;
15
+ if (typeof a === 'object') {
16
+ if (Object.keys(a).length !== Object.keys(b).length || Object.keys(a).some((k) => !(k in b)))
17
+ return false;
18
+ return Object.entries(a)
19
+ .filter(([k]) => !KEYS_TO_EXCLUDE.includes(k))
20
+ .every(([k, v]) => deepEqual(v, b[k]));
21
+ }
22
+ // boolean, string, number, undefined
23
+ return a === b;
24
+ };
25
+ return deepEqual;
26
+ };
27
+
28
+ const deepEqual = createDeepEqual();
29
+ /**
30
+ * Returns a version of `newValue` whose properties that are deeply equal to
31
+ * those in `oldValue` are replaced with those from `oldValue`. This provides a
32
+ * limited form of "structural sharing" that provides a stable reference for
33
+ * unchanged slices of the object.
34
+ *
35
+ * If `oldValue` and `newValue` are referentially equal, the same value is
36
+ * returned.
37
+ *
38
+ * @param oldValue The old value
39
+ * @param newValue The new value
40
+ */
41
+ const mergeWithStableProps = (oldValue, newValue) => {
42
+ // If the values are already referentially the same, just return the new value
43
+ if (oldValue === newValue) {
44
+ return newValue;
45
+ }
46
+ return Object.keys(oldValue).reduce((acc, key) => {
47
+ if (key in newValue && deepEqual(oldValue[key], newValue[key])) {
48
+ acc[key] = oldValue[key];
49
+ }
50
+ return acc;
51
+ }, Object.assign({}, newValue));
52
+ };
53
+
3
54
  const noProviderError = (item, provider = 'StytchProvider') => `${item} can only be used inside <${provider}>.`;
4
55
  const noHeadlessClientError = `Tried to create a Stytch Login UI element using the Stytch Headless SDK.
5
56
  You must use the UI SDK to use UI elements.
@@ -256,53 +307,35 @@ const withStytchPermissions = (Component) => {
256
307
  */
257
308
  const StytchB2BProvider = ({ stytch, children }) => {
258
309
  const ctx = useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
259
- const [member, setMember] = useAsyncState(initialMember);
260
- const [session, setMemberSession] = useAsyncState(initialMemberSession);
261
- const [organization, setOrganization] = useAsyncState(initialOrganization);
310
+ const [{ member, session, organization }, setClientState] = useAsyncState({
311
+ member: initialMember,
312
+ session: initialMemberSession,
313
+ organization: initialOrganization,
314
+ });
262
315
  useEffect(() => {
263
316
  if (isStytchSSRProxy(stytch)) {
264
317
  return;
265
318
  }
266
- setMember({
267
- member: stytch.self.getSync(),
268
- fromCache: true,
269
- isInitialized: true,
319
+ setClientState({
320
+ member: Object.assign(Object.assign({}, stytch.self.getInfo()), { isInitialized: true }),
321
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
322
+ organization: Object.assign(Object.assign({}, stytch.organization.getInfo()), { isInitialized: true }),
270
323
  });
271
- setMemberSession({
272
- session: stytch.session.getSync(),
273
- fromCache: true,
274
- isInitialized: true,
324
+ return stytch.onStateChange(() => {
325
+ setClientState((oldState) => {
326
+ const newState = {
327
+ member: Object.assign(Object.assign({}, stytch.self.getInfo()), { isInitialized: true }),
328
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
329
+ organization: Object.assign(Object.assign({}, stytch.organization.getInfo()), { isInitialized: true }),
330
+ };
331
+ return mergeWithStableProps(oldState, newState);
332
+ });
275
333
  });
276
- setOrganization({
277
- organization: stytch.organization.getSync(),
278
- fromCache: true,
279
- isInitialized: true,
280
- });
281
- const unsubscribeMember = stytch.self.onChange((member) => setMember({ member, fromCache: false, isInitialized: true }));
282
- const unsubscribeMemberSession = stytch.session.onChange((session) => setMemberSession({ session, fromCache: false, isInitialized: true }));
283
- const unsubscribeOrganization = stytch.organization.onChange((organization) => setOrganization({ organization, fromCache: false, isInitialized: true }));
284
- return () => {
285
- unsubscribeMember();
286
- unsubscribeMemberSession();
287
- unsubscribeOrganization();
288
- };
289
- }, [stytch, setMember, setMemberSession, setOrganization]);
290
- const allValuesReady = !!member.member === !!session.session && !!session.session === !!organization.organization;
291
- const finalValues = allValuesReady
292
- ? {
293
- member,
294
- session,
295
- organization,
296
- }
297
- : {
298
- member: initialMember,
299
- session: initialMemberSession,
300
- organization: initialOrganization,
301
- };
334
+ }, [setClientState, stytch]);
302
335
  return (React.createElement(StytchContext.Provider, { value: ctx },
303
- React.createElement(StytchOrganizationContext.Provider, { value: finalValues.organization },
304
- React.createElement(StytchMemberContext.Provider, { value: finalValues.member },
305
- React.createElement(StytchMemberSessionContext.Provider, { value: finalValues.session }, children)))));
336
+ React.createElement(StytchOrganizationContext.Provider, { value: organization },
337
+ React.createElement(StytchMemberContext.Provider, { value: member },
338
+ React.createElement(StytchMemberSessionContext.Provider, { value: session }, children)))));
306
339
  };
307
340
 
308
341
  /**
@@ -12,5 +12,5 @@ import { StytchB2BHeadlessClient } from "@stytch/vanilla-js/b2b/headless";
12
12
  * )
13
13
  * @returns A {@link StytchB2BHeadlessClient}
14
14
  */
15
- declare const createStytchB2BHeadlessClient: (_PUBLIC_TOKEN: string, options?: import("@stytch/core/dist/public").StytchClientOptions | undefined) => StytchB2BHeadlessClient;
15
+ declare const createStytchB2BHeadlessClient: (_PUBLIC_TOKEN: string, options?: import("core/dist/public").StytchClientOptions | undefined) => StytchB2BHeadlessClient;
16
16
  export { createStytchB2BHeadlessClient };
@@ -12,5 +12,5 @@ import { StytchB2BHeadlessClient } from "@stytch/vanilla-js/b2b/headless";
12
12
  * )
13
13
  * @returns A {@link StytchB2BHeadlessClient}
14
14
  */
15
- declare const createStytchB2BHeadlessClient: (_PUBLIC_TOKEN: string, options?: import("@stytch/core/dist/public").StytchClientOptions | undefined) => StytchB2BHeadlessClient;
15
+ declare const createStytchB2BHeadlessClient: (_PUBLIC_TOKEN: string, options?: import("core/dist/public").StytchClientOptions | undefined) => StytchB2BHeadlessClient;
16
16
  export { createStytchB2BHeadlessClient };
package/dist/b2b/index.js CHANGED
@@ -8,6 +8,57 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
8
8
 
9
9
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
10
10
 
11
+ const createDeepEqual = ({ KEYS_TO_EXCLUDE = [] } = {}) => {
12
+ // If comparing functions, this may need some work. Not sure the
13
+ // best path for this: compare instance (what it currently does),
14
+ // stringify and compare, etc.
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ const deepEqual = (a, b) => {
17
+ // Ensures type is the same
18
+ if (typeof a !== typeof b)
19
+ return false;
20
+ // arrays, null, and objects all have type 'object'
21
+ if (a === null || b === null)
22
+ return a === b;
23
+ if (typeof a === 'object') {
24
+ if (Object.keys(a).length !== Object.keys(b).length || Object.keys(a).some((k) => !(k in b)))
25
+ return false;
26
+ return Object.entries(a)
27
+ .filter(([k]) => !KEYS_TO_EXCLUDE.includes(k))
28
+ .every(([k, v]) => deepEqual(v, b[k]));
29
+ }
30
+ // boolean, string, number, undefined
31
+ return a === b;
32
+ };
33
+ return deepEqual;
34
+ };
35
+
36
+ const deepEqual = createDeepEqual();
37
+ /**
38
+ * Returns a version of `newValue` whose properties that are deeply equal to
39
+ * those in `oldValue` are replaced with those from `oldValue`. This provides a
40
+ * limited form of "structural sharing" that provides a stable reference for
41
+ * unchanged slices of the object.
42
+ *
43
+ * If `oldValue` and `newValue` are referentially equal, the same value is
44
+ * returned.
45
+ *
46
+ * @param oldValue The old value
47
+ * @param newValue The new value
48
+ */
49
+ const mergeWithStableProps = (oldValue, newValue) => {
50
+ // If the values are already referentially the same, just return the new value
51
+ if (oldValue === newValue) {
52
+ return newValue;
53
+ }
54
+ return Object.keys(oldValue).reduce((acc, key) => {
55
+ if (key in newValue && deepEqual(oldValue[key], newValue[key])) {
56
+ acc[key] = oldValue[key];
57
+ }
58
+ return acc;
59
+ }, Object.assign({}, newValue));
60
+ };
61
+
11
62
  const noProviderError = (item, provider = 'StytchProvider') => `${item} can only be used inside <${provider}>.`;
12
63
  const noHeadlessClientError = `Tried to create a Stytch Login UI element using the Stytch Headless SDK.
13
64
  You must use the UI SDK to use UI elements.
@@ -264,53 +315,35 @@ const withStytchPermissions = (Component) => {
264
315
  */
265
316
  const StytchB2BProvider = ({ stytch, children }) => {
266
317
  const ctx = React.useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
267
- const [member, setMember] = useAsyncState(initialMember);
268
- const [session, setMemberSession] = useAsyncState(initialMemberSession);
269
- const [organization, setOrganization] = useAsyncState(initialOrganization);
318
+ const [{ member, session, organization }, setClientState] = useAsyncState({
319
+ member: initialMember,
320
+ session: initialMemberSession,
321
+ organization: initialOrganization,
322
+ });
270
323
  React.useEffect(() => {
271
324
  if (isStytchSSRProxy(stytch)) {
272
325
  return;
273
326
  }
274
- setMember({
275
- member: stytch.self.getSync(),
276
- fromCache: true,
277
- isInitialized: true,
327
+ setClientState({
328
+ member: Object.assign(Object.assign({}, stytch.self.getInfo()), { isInitialized: true }),
329
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
330
+ organization: Object.assign(Object.assign({}, stytch.organization.getInfo()), { isInitialized: true }),
278
331
  });
279
- setMemberSession({
280
- session: stytch.session.getSync(),
281
- fromCache: true,
282
- isInitialized: true,
332
+ return stytch.onStateChange(() => {
333
+ setClientState((oldState) => {
334
+ const newState = {
335
+ member: Object.assign(Object.assign({}, stytch.self.getInfo()), { isInitialized: true }),
336
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
337
+ organization: Object.assign(Object.assign({}, stytch.organization.getInfo()), { isInitialized: true }),
338
+ };
339
+ return mergeWithStableProps(oldState, newState);
340
+ });
283
341
  });
284
- setOrganization({
285
- organization: stytch.organization.getSync(),
286
- fromCache: true,
287
- isInitialized: true,
288
- });
289
- const unsubscribeMember = stytch.self.onChange((member) => setMember({ member, fromCache: false, isInitialized: true }));
290
- const unsubscribeMemberSession = stytch.session.onChange((session) => setMemberSession({ session, fromCache: false, isInitialized: true }));
291
- const unsubscribeOrganization = stytch.organization.onChange((organization) => setOrganization({ organization, fromCache: false, isInitialized: true }));
292
- return () => {
293
- unsubscribeMember();
294
- unsubscribeMemberSession();
295
- unsubscribeOrganization();
296
- };
297
- }, [stytch, setMember, setMemberSession, setOrganization]);
298
- const allValuesReady = !!member.member === !!session.session && !!session.session === !!organization.organization;
299
- const finalValues = allValuesReady
300
- ? {
301
- member,
302
- session,
303
- organization,
304
- }
305
- : {
306
- member: initialMember,
307
- session: initialMemberSession,
308
- organization: initialOrganization,
309
- };
342
+ }, [setClientState, stytch]);
310
343
  return (React__default['default'].createElement(StytchContext.Provider, { value: ctx },
311
- React__default['default'].createElement(StytchOrganizationContext.Provider, { value: finalValues.organization },
312
- React__default['default'].createElement(StytchMemberContext.Provider, { value: finalValues.member },
313
- React__default['default'].createElement(StytchMemberSessionContext.Provider, { value: finalValues.session }, children)))));
344
+ React__default['default'].createElement(StytchOrganizationContext.Provider, { value: organization },
345
+ React__default['default'].createElement(StytchMemberContext.Provider, { value: member },
346
+ React__default['default'].createElement(StytchMemberSessionContext.Provider, { value: session }, children)))));
314
347
  };
315
348
 
316
349
  /**
@@ -13,5 +13,5 @@ import { StytchB2BUIClient } from "@stytch/vanilla-js/b2b";
13
13
  * )
14
14
  * @returns A {@link StytchB2BUIClient}
15
15
  */
16
- declare const createStytchB2BUIClient: (_PUBLIC_TOKEN: string, options?: import("@stytch/core/dist/public").StytchClientOptions | undefined) => StytchB2BUIClient;
16
+ declare const createStytchB2BUIClient: (_PUBLIC_TOKEN: string, options?: import("core/dist/public").StytchClientOptions | undefined) => StytchB2BUIClient;
17
17
  export { createStytchB2BUIClient };
@@ -13,5 +13,5 @@ import { StytchB2BUIClient } from "@stytch/vanilla-js/b2b";
13
13
  * )
14
14
  * @returns A {@link StytchB2BUIClient}
15
15
  */
16
- declare const createStytchB2BUIClient: (_PUBLIC_TOKEN: string, options?: import("@stytch/core/dist/public").StytchClientOptions | undefined) => StytchB2BUIClient;
16
+ declare const createStytchB2BUIClient: (_PUBLIC_TOKEN: string, options?: import("core/dist/public").StytchClientOptions | undefined) => StytchB2BUIClient;
17
17
  export { createStytchB2BUIClient };
package/dist/index.esm.js CHANGED
@@ -1,5 +1,56 @@
1
1
  import React, { useRef, useState, useEffect, useCallback, createContext, useContext, useMemo, useLayoutEffect } from 'react';
2
2
 
3
+ const createDeepEqual = ({ KEYS_TO_EXCLUDE = [] } = {}) => {
4
+ // If comparing functions, this may need some work. Not sure the
5
+ // best path for this: compare instance (what it currently does),
6
+ // stringify and compare, etc.
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ const deepEqual = (a, b) => {
9
+ // Ensures type is the same
10
+ if (typeof a !== typeof b)
11
+ return false;
12
+ // arrays, null, and objects all have type 'object'
13
+ if (a === null || b === null)
14
+ return a === b;
15
+ if (typeof a === 'object') {
16
+ if (Object.keys(a).length !== Object.keys(b).length || Object.keys(a).some((k) => !(k in b)))
17
+ return false;
18
+ return Object.entries(a)
19
+ .filter(([k]) => !KEYS_TO_EXCLUDE.includes(k))
20
+ .every(([k, v]) => deepEqual(v, b[k]));
21
+ }
22
+ // boolean, string, number, undefined
23
+ return a === b;
24
+ };
25
+ return deepEqual;
26
+ };
27
+
28
+ const deepEqual = createDeepEqual();
29
+ /**
30
+ * Returns a version of `newValue` whose properties that are deeply equal to
31
+ * those in `oldValue` are replaced with those from `oldValue`. This provides a
32
+ * limited form of "structural sharing" that provides a stable reference for
33
+ * unchanged slices of the object.
34
+ *
35
+ * If `oldValue` and `newValue` are referentially equal, the same value is
36
+ * returned.
37
+ *
38
+ * @param oldValue The old value
39
+ * @param newValue The new value
40
+ */
41
+ const mergeWithStableProps = (oldValue, newValue) => {
42
+ // If the values are already referentially the same, just return the new value
43
+ if (oldValue === newValue) {
44
+ return newValue;
45
+ }
46
+ return Object.keys(oldValue).reduce((acc, key) => {
47
+ if (key in newValue && deepEqual(oldValue[key], newValue[key])) {
48
+ acc[key] = oldValue[key];
49
+ }
50
+ return acc;
51
+ }, Object.assign({}, newValue));
52
+ };
53
+
3
54
  const noProviderError = (item, provider = 'StytchProvider') => `${item} can only be used inside <${provider}>.`;
4
55
  const noHeadlessClientError = `Tried to create a Stytch Login UI element using the Stytch Headless SDK.
5
56
  You must use the UI SDK to use UI elements.
@@ -143,35 +194,31 @@ const withStytchSession = (Component) => {
143
194
  */
144
195
  const StytchProvider = ({ stytch, children }) => {
145
196
  const ctx = useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
146
- const [user, setUser] = useAsyncState(initialUser);
147
- const [session, setSession] = useAsyncState(initialSession);
197
+ const [{ user, session }, setClientState] = useAsyncState({
198
+ session: initialSession,
199
+ user: initialUser,
200
+ });
148
201
  useEffect(() => {
149
202
  if (isStytchSSRProxy(stytch)) {
150
203
  return;
151
204
  }
152
- setUser({
153
- user: stytch.user.getSync(),
154
- fromCache: true,
155
- isInitialized: true,
205
+ setClientState({
206
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
207
+ user: Object.assign(Object.assign({}, stytch.user.getInfo()), { isInitialized: true }),
156
208
  });
157
- setSession({
158
- session: stytch.session.getSync(),
159
- fromCache: true,
160
- isInitialized: true,
209
+ return stytch.onStateChange(() => {
210
+ setClientState((oldState) => {
211
+ const newState = {
212
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
213
+ user: Object.assign(Object.assign({}, stytch.user.getInfo()), { isInitialized: true }),
214
+ };
215
+ return mergeWithStableProps(oldState, newState);
216
+ });
161
217
  });
162
- const unsubscribeUser = stytch.user.onChange((user) => setUser({ user, fromCache: false, isInitialized: true }));
163
- const unsubscribeSession = stytch.session.onChange((session) => setSession({ session, fromCache: false, isInitialized: true }));
164
- return () => {
165
- unsubscribeUser();
166
- unsubscribeSession();
167
- };
168
- }, [stytch, setUser, setSession]);
169
- // TODO (SDK-813): Remove this when we have a single top-level onChange handler
170
- const finalSess = !!session.session === !!user.user ? session : initialSession;
171
- const finalUser = !!session.session === !!user.user ? user : initialUser;
218
+ }, [setClientState, stytch]);
172
219
  return (React.createElement(StytchContext.Provider, { value: ctx },
173
- React.createElement(StytchUserContext.Provider, { value: finalUser },
174
- React.createElement(StytchSessionContext.Provider, { value: finalSess }, children))));
220
+ React.createElement(StytchUserContext.Provider, { value: user },
221
+ React.createElement(StytchSessionContext.Provider, { value: session }, children))));
175
222
  };
176
223
 
177
224
  // cc https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a
@@ -1,3 +1,4 @@
1
+ import { StytchHeadlessClient } from "@stytch/vanilla-js/headless";
1
2
  /**
2
3
  * Creates a Headless Stytch client object to call the stytch APIs.
3
4
  * The Stytch client is not available serverside.
@@ -11,5 +12,5 @@
11
12
  * )
12
13
  * @returns A {@link StytchHeadlessClient}
13
14
  */
14
- declare const createStytchHeadlessClient: any;
15
+ declare const createStytchHeadlessClient: (_PUBLIC_TOKEN: string, options?: import("core/dist/public").StytchClientOptions | undefined) => StytchHeadlessClient;
15
16
  export { createStytchHeadlessClient };
@@ -1,3 +1,4 @@
1
+ import { StytchHeadlessClient } from "@stytch/vanilla-js/headless";
1
2
  /**
2
3
  * Creates a Headless Stytch client object to call the stytch APIs.
3
4
  * The Stytch client is not available serverside.
@@ -11,5 +12,5 @@
11
12
  * )
12
13
  * @returns A {@link StytchHeadlessClient}
13
14
  */
14
- declare const createStytchHeadlessClient: any;
15
+ declare const createStytchHeadlessClient: (_PUBLIC_TOKEN: string, options?: import("core/dist/public").StytchClientOptions | undefined) => StytchHeadlessClient;
15
16
  export { createStytchHeadlessClient };
package/dist/index.js CHANGED
@@ -8,6 +8,57 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
8
8
 
9
9
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
10
10
 
11
+ const createDeepEqual = ({ KEYS_TO_EXCLUDE = [] } = {}) => {
12
+ // If comparing functions, this may need some work. Not sure the
13
+ // best path for this: compare instance (what it currently does),
14
+ // stringify and compare, etc.
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ const deepEqual = (a, b) => {
17
+ // Ensures type is the same
18
+ if (typeof a !== typeof b)
19
+ return false;
20
+ // arrays, null, and objects all have type 'object'
21
+ if (a === null || b === null)
22
+ return a === b;
23
+ if (typeof a === 'object') {
24
+ if (Object.keys(a).length !== Object.keys(b).length || Object.keys(a).some((k) => !(k in b)))
25
+ return false;
26
+ return Object.entries(a)
27
+ .filter(([k]) => !KEYS_TO_EXCLUDE.includes(k))
28
+ .every(([k, v]) => deepEqual(v, b[k]));
29
+ }
30
+ // boolean, string, number, undefined
31
+ return a === b;
32
+ };
33
+ return deepEqual;
34
+ };
35
+
36
+ const deepEqual = createDeepEqual();
37
+ /**
38
+ * Returns a version of `newValue` whose properties that are deeply equal to
39
+ * those in `oldValue` are replaced with those from `oldValue`. This provides a
40
+ * limited form of "structural sharing" that provides a stable reference for
41
+ * unchanged slices of the object.
42
+ *
43
+ * If `oldValue` and `newValue` are referentially equal, the same value is
44
+ * returned.
45
+ *
46
+ * @param oldValue The old value
47
+ * @param newValue The new value
48
+ */
49
+ const mergeWithStableProps = (oldValue, newValue) => {
50
+ // If the values are already referentially the same, just return the new value
51
+ if (oldValue === newValue) {
52
+ return newValue;
53
+ }
54
+ return Object.keys(oldValue).reduce((acc, key) => {
55
+ if (key in newValue && deepEqual(oldValue[key], newValue[key])) {
56
+ acc[key] = oldValue[key];
57
+ }
58
+ return acc;
59
+ }, Object.assign({}, newValue));
60
+ };
61
+
11
62
  const noProviderError = (item, provider = 'StytchProvider') => `${item} can only be used inside <${provider}>.`;
12
63
  const noHeadlessClientError = `Tried to create a Stytch Login UI element using the Stytch Headless SDK.
13
64
  You must use the UI SDK to use UI elements.
@@ -151,35 +202,31 @@ const withStytchSession = (Component) => {
151
202
  */
152
203
  const StytchProvider = ({ stytch, children }) => {
153
204
  const ctx = React.useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
154
- const [user, setUser] = useAsyncState(initialUser);
155
- const [session, setSession] = useAsyncState(initialSession);
205
+ const [{ user, session }, setClientState] = useAsyncState({
206
+ session: initialSession,
207
+ user: initialUser,
208
+ });
156
209
  React.useEffect(() => {
157
210
  if (isStytchSSRProxy(stytch)) {
158
211
  return;
159
212
  }
160
- setUser({
161
- user: stytch.user.getSync(),
162
- fromCache: true,
163
- isInitialized: true,
213
+ setClientState({
214
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
215
+ user: Object.assign(Object.assign({}, stytch.user.getInfo()), { isInitialized: true }),
164
216
  });
165
- setSession({
166
- session: stytch.session.getSync(),
167
- fromCache: true,
168
- isInitialized: true,
217
+ return stytch.onStateChange(() => {
218
+ setClientState((oldState) => {
219
+ const newState = {
220
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
221
+ user: Object.assign(Object.assign({}, stytch.user.getInfo()), { isInitialized: true }),
222
+ };
223
+ return mergeWithStableProps(oldState, newState);
224
+ });
169
225
  });
170
- const unsubscribeUser = stytch.user.onChange((user) => setUser({ user, fromCache: false, isInitialized: true }));
171
- const unsubscribeSession = stytch.session.onChange((session) => setSession({ session, fromCache: false, isInitialized: true }));
172
- return () => {
173
- unsubscribeUser();
174
- unsubscribeSession();
175
- };
176
- }, [stytch, setUser, setSession]);
177
- // TODO (SDK-813): Remove this when we have a single top-level onChange handler
178
- const finalSess = !!session.session === !!user.user ? session : initialSession;
179
- const finalUser = !!session.session === !!user.user ? user : initialUser;
226
+ }, [setClientState, stytch]);
180
227
  return (React__default['default'].createElement(StytchContext.Provider, { value: ctx },
181
- React__default['default'].createElement(StytchUserContext.Provider, { value: finalUser },
182
- React__default['default'].createElement(StytchSessionContext.Provider, { value: finalSess }, children))));
228
+ React__default['default'].createElement(StytchUserContext.Provider, { value: user },
229
+ React__default['default'].createElement(StytchSessionContext.Provider, { value: session }, children))));
183
230
  };
184
231
 
185
232
  // cc https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a
@@ -1,3 +1,4 @@
1
+ import { StytchUIClient } from "@stytch/vanilla-js";
1
2
  /**
2
3
  * Creates a Stytch UI client object to call the Stytch APIs and render Stytch UI components.
3
4
  * The Stytch client is not available serverside.
@@ -12,5 +13,5 @@
12
13
  * )
13
14
  * @returns A {@link StytchUIClient}
14
15
  */
15
- declare const createStytchUIClient: any;
16
+ declare const createStytchUIClient: (_PUBLIC_TOKEN: string, options?: import("core/dist/public").StytchClientOptions | undefined) => StytchUIClient;
16
17
  export { createStytchUIClient };
@@ -1,3 +1,4 @@
1
+ import { StytchUIClient } from "@stytch/vanilla-js";
1
2
  /**
2
3
  * Creates a Stytch UI client object to call the Stytch APIs and render Stytch UI components.
3
4
  * The Stytch client is not available serverside.
@@ -12,5 +13,5 @@
12
13
  * )
13
14
  * @returns A {@link StytchUIClient}
14
15
  */
15
- declare const createStytchUIClient: any;
16
+ declare const createStytchUIClient: (_PUBLIC_TOKEN: string, options?: import("core/dist/public").StytchClientOptions | undefined) => StytchUIClient;
16
17
  export { createStytchUIClient };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stytch/nextjs",
3
- "version": "18.0.0",
3
+ "version": "20.0.0",
4
4
  "description": "Stytch's official Next.js Library",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",
@@ -22,7 +22,8 @@
22
22
  "author": "Stytch",
23
23
  "devDependencies": {
24
24
  "@babel/runtime": "7.18.6",
25
- "@stytch/vanilla-js": "4.7.0",
25
+ "@stytch/js-utils": "0.0.0",
26
+ "@stytch/vanilla-js": "4.12.0",
26
27
  "@testing-library/react": "14.0.0",
27
28
  "eslint-config-custom": "0.0.1",
28
29
  "react": "18.2.0",
@@ -32,7 +33,7 @@
32
33
  "typescript": "5.3.3"
33
34
  },
34
35
  "peerDependencies": {
35
- "@stytch/vanilla-js": "^4.7.0",
36
+ "@stytch/vanilla-js": "^4.12.0",
36
37
  "react": ">= 17.0.2",
37
38
  "react-dom": ">= 17.0.2"
38
39
  },