@stytch/react 0.0.1 → 0.1.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Stytch-React
1
+ # @stytch/react
2
2
 
3
3
  Stytch's React Library
4
4
 
@@ -9,7 +9,7 @@ With `npm`
9
9
 
10
10
  ## Documentation
11
11
 
12
- For full documentation please refer to Stytch's javascript SDK documentation on https://stytch.com/docs/sdks.
12
+ For full documentation please refer to Stytch's javascript SDK documentation at https://stytch.com/docs/sdks.
13
13
 
14
14
  ## Example Usage
15
15
 
@@ -30,7 +30,7 @@ ReactDOM.render(
30
30
  // Now use Stytch in your components
31
31
  const App = () => {
32
32
  const stytchProps = {
33
- loginOrSignupView: {
33
+ config: {
34
34
  products: ['emailMagicLinks'],
35
35
  emailMagicLinksOptions: {
36
36
  loginRedirectURL: 'https://example.com/authenticate',
@@ -40,7 +40,7 @@ const App = () => {
40
40
  createUserAsPending: true,
41
41
  },
42
42
  },
43
- style: {
43
+ styles: {
44
44
  fontFamily: '"Helvetica New", Helvetica, sans-serif',
45
45
  width: '321px',
46
46
  primaryColor: '#0577CA',
@@ -54,10 +54,9 @@ const App = () => {
54
54
 
55
55
  return (
56
56
  <div id="login">
57
- <Stytch
58
- publicToken={stytchProps.publicToken}
59
- loginOrSignupView={stytchProps.loginOrSignupView}
60
- style={stytchProps.style}
57
+ <StytchLogin
58
+ config={stytchProps.loginOrSignupView}
59
+ styles={stytchProps.style}
61
60
  callbacks={stytchProps.callbacks}
62
61
  />
63
62
  </div>
package/dist/index.d.ts CHANGED
@@ -1,32 +1,193 @@
1
1
  /// <reference types="react" />
2
- import { Config, StyleConfig, User, Session, StytchUIClient } from "@stytch/vanilla-js";
3
- import * as React from "react";
2
+ // We need to import the StytchUIClient type to give the TSDoc parser a hint as to where it is from
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
+ import { Callbacks, StytchLoginConfig, StyleConfig, User, Session, StytchUIClient } from "@stytch/vanilla-js";
5
+ import React from "react";
6
+ import { ReactNode } from "react";
4
7
  import { StytchHeadlessClient } from "@stytch/vanilla-js/headless";
8
+ interface StytchProps {
9
+ /**
10
+ * A {@link StytchLoginConfig} object. Add products and product-specific config to this object to change the login methods shown.
11
+ *
12
+ * @example
13
+ * {
14
+ * products: ['crypto', 'otps']
15
+ * }
16
+ *
17
+ * @example
18
+ * {
19
+ * products: ['emailMagicLinks'>]
20
+ * emailMagicLinksOptions: {
21
+ * loginRedirectURL: 'https://example.com/authenticate',
22
+ * signupRedirectURL: 'https://example.com/authenticate',
23
+ * }
24
+ * }
25
+ *
26
+ * @example
27
+ * {
28
+ * products: ['oauth'>]
29
+ * oauthOptions: {
30
+ * providers: [
31
+ * { type: 'google', one_tap: true, position: 'embedded' },
32
+ * { type: 'microsoft' },
33
+ * { type: 'apple' },
34
+ * { type: 'facebook' },
35
+ * ],
36
+ * }
37
+ * }
38
+ */
39
+ config: StytchLoginConfig;
40
+ /**
41
+ * An optional {@link StyleConfig} to customize the look and feel of the screen.
42
+ *
43
+ * @example
44
+ * {
45
+ * fontFamily: 'Arial, Helvetica, sans-serif',
46
+ * width: '360px',
47
+ * primaryColor: '#19303D',
48
+ * }
49
+ */
50
+ styles?: StyleConfig;
51
+ /**
52
+ * An optional {@link Callbacks} object.
53
+ *
54
+ * @example
55
+ * {
56
+ * onError: ({message}) => {
57
+ * console.error('Stytch login screen error', message)
58
+ * }
59
+ * }
60
+ *
61
+ * @example
62
+ * {
63
+ * onEvent: ({type, data}) => {
64
+ * if(type === StytchEventType.CryptoWalletAuthenticate) {
65
+ * console.log('Logged in with crypto wallet', data);
66
+ * }
67
+ * }
68
+ * }
69
+ */
70
+ callbacks?: Callbacks;
71
+ }
5
72
  /**
6
- * Stytch JS React Component
73
+ * The Stytch Login Screen component.
74
+ * This component can only be used with a {@link StytchUIClient} client constructor
75
+ * passed into the {@link StytchProvider}
7
76
  *
8
- * [Documentation](https://stytch.com/docs/javascript-sdk)
77
+ * See the {@link https://stytch.com/docs/sdks/javascript-sdk online reference}
78
+ * and {@link https://storybook.stytch.com interactive examples} for more.
79
+ *
80
+ * @example
81
+ * <StytchLogin
82
+ * config={{
83
+ * products: ['emailMagicLinks', 'oauth'],
84
+ * emailMagicLinksOptions: {
85
+ * loginRedirectURL: 'https://example.com/authenticate',
86
+ * signupRedirectURL: 'https://example.com/authenticate',
87
+ * },
88
+ * oauthOptions: {
89
+ * providers: [{ type: OAuthProviders.Google }, { type: OAuthProviders.Microsoft }],
90
+ * },
91
+ * }}
92
+ * styles={{
93
+ * fontFamily: '"Helvetica New", Helvetica, sans-serif',
94
+ * primaryColor: '#0577CA',
95
+ * width: '321px',
96
+ * }}
97
+ * callbacks={{
98
+ * onEvent: (event) => console.log(event)
99
+ * }}
100
+ * />
101
+ * @param props {@link StytchProps}
102
+ */
103
+ declare const StytchLogin: ({ config, styles, callbacks }: StytchProps) => JSX.Element;
104
+ /**
105
+ * The Stytch Client object passed in to <StytchProvider /> in your application root.
106
+ * Either a StytchUIClient or StytchHeadlessClient.
9
107
  */
10
- declare const Stytch: ({ config, styles }: {
11
- config: Config;
12
- styles: StyleConfig;
13
- }) => JSX.Element | null;
14
108
  type StytchClient = StytchUIClient | StytchHeadlessClient;
15
- declare const useStytchUser: () => User | null;
16
- declare const useStytchSession: () => Session | null;
109
+ type SWRUser = {
110
+ /**
111
+ * Either the active {@link User} object, or null if the user is not logged in.
112
+ */
113
+ user: User | null;
114
+ /**
115
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
116
+ */
117
+ fromCache: boolean;
118
+ };
119
+ type SWRSession = {
120
+ /**
121
+ * Either the active {@link Session} object, or null if the user is not logged in.
122
+ */
123
+ session: Session | null;
124
+ /**
125
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
126
+ */
127
+ fromCache: boolean;
128
+ };
129
+ /**
130
+ * Returns the active User.
131
+ * Check the fromCache property to determine if the user data is from persistent storage.
132
+ * @example
133
+ * const {user} = useStytchUser();
134
+ * return (<h1>Welcome, {user.name.first_name}</h1>);
135
+ * @returns A {@link SWRUser}
136
+ */
137
+ declare const useStytchUser: () => SWRUser;
138
+ /**
139
+ * Returns the active user's Stytch session.
140
+ * @example
141
+ * const {session} = useStytchSession();
142
+ * useEffect(() => {
143
+ * if (!session) {
144
+ * router.replace('/login')
145
+ * }
146
+ * }, [session]);
147
+ * @returns A {@link SWRSession}
148
+ */
149
+ declare const useStytchSession: () => SWRSession;
150
+ /**
151
+ * Returns the Stytch client stored in the Stytch context.
152
+ *
153
+ * @example
154
+ * const stytch = useStytch();
155
+ * useEffect(() => {
156
+ * stytch.magicLinks.authenticate('...')
157
+ * }, [stytch]);
158
+ */
17
159
  declare const useStytch: () => StytchClient;
18
160
  declare const withStytch: <T extends object>(Component: React.ComponentType<T & {
19
161
  stytch: StytchClient;
20
162
  }>) => React.ComponentType<T>;
21
163
  declare const withStytchUser: <T extends object>(Component: React.ComponentType<T & {
22
164
  stytchUser: User | null;
165
+ stytchUserIsFromCache: boolean;
23
166
  }>) => React.ComponentType<T>;
24
167
  declare const withStytchSession: <T extends object>(Component: React.ComponentType<T & {
25
168
  stytchSession: Session | null;
169
+ stytchSessionIsFromCache: boolean;
26
170
  }>) => React.ComponentType<T>;
27
171
  type StytchProviderProps = {
172
+ /**
173
+ * A Stytch client instance, either a {@link StytchUIClient} or {@link StytchHeadlessClient}
174
+ */
28
175
  stytch: StytchClient;
29
- children?: React.ReactNode;
176
+ children?: ReactNode;
30
177
  };
178
+ /**
179
+ * The Stytch Context Provider.
180
+ * Wrap your application with this component in the root file in order to use Stytch everywhere in your app.
181
+ * @example
182
+ * const stytch = new StytchHeadlessClient('public-token-<find yours in the stytch dashboard>')
183
+ *
184
+ * ReactDOM.render(
185
+ * <StytchProvider stytch={stytch}>
186
+ * <App />
187
+ * </StytchProvider>,
188
+ * document.getElementById('root'),
189
+ * )
190
+ */
31
191
  declare const StytchProvider: ({ stytch, children }: StytchProviderProps) => JSX.Element;
32
- export { Stytch, useStytchUser, useStytchSession, useStytch, withStytch, withStytchUser, withStytchSession, StytchProviderProps, StytchProvider };
192
+ export { StytchLogin, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
193
+ export type { StytchProviderProps };
@@ -1,32 +1,193 @@
1
1
  /// <reference types="react" />
2
- import { Config, StyleConfig, User, Session, StytchUIClient } from "@stytch/vanilla-js";
3
- import * as React from "react";
2
+ // We need to import the StytchUIClient type to give the TSDoc parser a hint as to where it is from
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
+ import { Callbacks, StytchLoginConfig, StyleConfig, User, Session, StytchUIClient } from "@stytch/vanilla-js";
5
+ import React from "react";
6
+ import { ReactNode } from "react";
4
7
  import { StytchHeadlessClient } from "@stytch/vanilla-js/headless";
8
+ interface StytchProps {
9
+ /**
10
+ * A {@link StytchLoginConfig} object. Add products and product-specific config to this object to change the login methods shown.
11
+ *
12
+ * @example
13
+ * {
14
+ * products: ['crypto', 'otps']
15
+ * }
16
+ *
17
+ * @example
18
+ * {
19
+ * products: ['emailMagicLinks'>]
20
+ * emailMagicLinksOptions: {
21
+ * loginRedirectURL: 'https://example.com/authenticate',
22
+ * signupRedirectURL: 'https://example.com/authenticate',
23
+ * }
24
+ * }
25
+ *
26
+ * @example
27
+ * {
28
+ * products: ['oauth'>]
29
+ * oauthOptions: {
30
+ * providers: [
31
+ * { type: 'google', one_tap: true, position: 'embedded' },
32
+ * { type: 'microsoft' },
33
+ * { type: 'apple' },
34
+ * { type: 'facebook' },
35
+ * ],
36
+ * }
37
+ * }
38
+ */
39
+ config: StytchLoginConfig;
40
+ /**
41
+ * An optional {@link StyleConfig} to customize the look and feel of the screen.
42
+ *
43
+ * @example
44
+ * {
45
+ * fontFamily: 'Arial, Helvetica, sans-serif',
46
+ * width: '360px',
47
+ * primaryColor: '#19303D',
48
+ * }
49
+ */
50
+ styles?: StyleConfig;
51
+ /**
52
+ * An optional {@link Callbacks} object.
53
+ *
54
+ * @example
55
+ * {
56
+ * onError: ({message}) => {
57
+ * console.error('Stytch login screen error', message)
58
+ * }
59
+ * }
60
+ *
61
+ * @example
62
+ * {
63
+ * onEvent: ({type, data}) => {
64
+ * if(type === StytchEventType.CryptoWalletAuthenticate) {
65
+ * console.log('Logged in with crypto wallet', data);
66
+ * }
67
+ * }
68
+ * }
69
+ */
70
+ callbacks?: Callbacks;
71
+ }
5
72
  /**
6
- * Stytch JS React Component
73
+ * The Stytch Login Screen component.
74
+ * This component can only be used with a {@link StytchUIClient} client constructor
75
+ * passed into the {@link StytchProvider}
7
76
  *
8
- * [Documentation](https://stytch.com/docs/javascript-sdk)
77
+ * See the {@link https://stytch.com/docs/sdks/javascript-sdk online reference}
78
+ * and {@link https://storybook.stytch.com interactive examples} for more.
79
+ *
80
+ * @example
81
+ * <StytchLogin
82
+ * config={{
83
+ * products: ['emailMagicLinks', 'oauth'],
84
+ * emailMagicLinksOptions: {
85
+ * loginRedirectURL: 'https://example.com/authenticate',
86
+ * signupRedirectURL: 'https://example.com/authenticate',
87
+ * },
88
+ * oauthOptions: {
89
+ * providers: [{ type: OAuthProviders.Google }, { type: OAuthProviders.Microsoft }],
90
+ * },
91
+ * }}
92
+ * styles={{
93
+ * fontFamily: '"Helvetica New", Helvetica, sans-serif',
94
+ * primaryColor: '#0577CA',
95
+ * width: '321px',
96
+ * }}
97
+ * callbacks={{
98
+ * onEvent: (event) => console.log(event)
99
+ * }}
100
+ * />
101
+ * @param props {@link StytchProps}
102
+ */
103
+ declare const StytchLogin: ({ config, styles, callbacks }: StytchProps) => JSX.Element;
104
+ /**
105
+ * The Stytch Client object passed in to <StytchProvider /> in your application root.
106
+ * Either a StytchUIClient or StytchHeadlessClient.
9
107
  */
10
- declare const Stytch: ({ config, styles }: {
11
- config: Config;
12
- styles: StyleConfig;
13
- }) => JSX.Element | null;
14
108
  type StytchClient = StytchUIClient | StytchHeadlessClient;
15
- declare const useStytchUser: () => User | null;
16
- declare const useStytchSession: () => Session | null;
109
+ type SWRUser = {
110
+ /**
111
+ * Either the active {@link User} object, or null if the user is not logged in.
112
+ */
113
+ user: User | null;
114
+ /**
115
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
116
+ */
117
+ fromCache: boolean;
118
+ };
119
+ type SWRSession = {
120
+ /**
121
+ * Either the active {@link Session} object, or null if the user is not logged in.
122
+ */
123
+ session: Session | null;
124
+ /**
125
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
126
+ */
127
+ fromCache: boolean;
128
+ };
129
+ /**
130
+ * Returns the active User.
131
+ * Check the fromCache property to determine if the user data is from persistent storage.
132
+ * @example
133
+ * const {user} = useStytchUser();
134
+ * return (<h1>Welcome, {user.name.first_name}</h1>);
135
+ * @returns A {@link SWRUser}
136
+ */
137
+ declare const useStytchUser: () => SWRUser;
138
+ /**
139
+ * Returns the active user's Stytch session.
140
+ * @example
141
+ * const {session} = useStytchSession();
142
+ * useEffect(() => {
143
+ * if (!session) {
144
+ * router.replace('/login')
145
+ * }
146
+ * }, [session]);
147
+ * @returns A {@link SWRSession}
148
+ */
149
+ declare const useStytchSession: () => SWRSession;
150
+ /**
151
+ * Returns the Stytch client stored in the Stytch context.
152
+ *
153
+ * @example
154
+ * const stytch = useStytch();
155
+ * useEffect(() => {
156
+ * stytch.magicLinks.authenticate('...')
157
+ * }, [stytch]);
158
+ */
17
159
  declare const useStytch: () => StytchClient;
18
160
  declare const withStytch: <T extends object>(Component: React.ComponentType<T & {
19
161
  stytch: StytchClient;
20
162
  }>) => React.ComponentType<T>;
21
163
  declare const withStytchUser: <T extends object>(Component: React.ComponentType<T & {
22
164
  stytchUser: User | null;
165
+ stytchUserIsFromCache: boolean;
23
166
  }>) => React.ComponentType<T>;
24
167
  declare const withStytchSession: <T extends object>(Component: React.ComponentType<T & {
25
168
  stytchSession: Session | null;
169
+ stytchSessionIsFromCache: boolean;
26
170
  }>) => React.ComponentType<T>;
27
171
  type StytchProviderProps = {
172
+ /**
173
+ * A Stytch client instance, either a {@link StytchUIClient} or {@link StytchHeadlessClient}
174
+ */
28
175
  stytch: StytchClient;
29
- children?: React.ReactNode;
176
+ children?: ReactNode;
30
177
  };
178
+ /**
179
+ * The Stytch Context Provider.
180
+ * Wrap your application with this component in the root file in order to use Stytch everywhere in your app.
181
+ * @example
182
+ * const stytch = new StytchHeadlessClient('public-token-<find yours in the stytch dashboard>')
183
+ *
184
+ * ReactDOM.render(
185
+ * <StytchProvider stytch={stytch}>
186
+ * <App />
187
+ * </StytchProvider>,
188
+ * document.getElementById('root'),
189
+ * )
190
+ */
31
191
  declare const StytchProvider: ({ stytch, children }: StytchProviderProps) => JSX.Element;
32
- export { Stytch, useStytchUser, useStytchSession, useStytch, withStytch, withStytchUser, withStytchSession, StytchProviderProps, StytchProvider };
192
+ export { StytchLogin, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
193
+ export type { StytchProviderProps };
package/dist/index.esm.js CHANGED
@@ -1,94 +1,222 @@
1
- import * as React from 'react';
2
- import { StytchUIClient } from '@stytch/vanilla-js';
1
+ import React, { useRef, useState, useEffect, useCallback, createContext, useContext, useMemo, useLayoutEffect } from 'react';
3
2
 
4
- const StytchUserContext = React.createContext(null);
5
- const StytchSessionContext = React.createContext(null);
6
- const StytchContext = React.createContext({});
3
+ const noProviderError = (item) => `${item} can only be used inside <StytchProvider>.`;
4
+ const providerMustBeUniqueError = 'You cannot render a <StytchProvider> inside another <StytchProvider>.';
5
+ const noSSRError = `The @stytch/react library is not meant for use with serverside environments like NextJS.
6
+ Use the @stytch/nextjs library instead -
7
+ npm remove @stytch/react && npm install @stytch/nextjs
8
+ `;
9
+ const noHeadlessClientError = `Tried to create a Stytch Login UI element using the Stytch Headless SDK.
10
+ You must use the UI SDK to use UI elements.
11
+ Please make sure you are importing from @stytch/vanilla-js and not from the @stytch/vanilla-js/headless.`;
12
+
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ function invariant(cond, message) {
15
+ if (!cond)
16
+ throw new Error(message);
17
+ }
18
+
19
+ // useState can cause memory leaks if it is set after the component unmounted. For example, if it is
20
+ // set after `await`, or in a `then`, `catch`, or `finally`, or in a setTimout/setInterval.
21
+ const useAsyncState = (initialState) => {
22
+ const isMounted = useRef(true);
23
+ const [state, setState] = useState(initialState);
24
+ useEffect(() => {
25
+ isMounted.current = true;
26
+ return () => {
27
+ isMounted.current = false;
28
+ };
29
+ }, []);
30
+ const setStateAction = useCallback((newState) => {
31
+ isMounted.current && setState(newState);
32
+ }, []);
33
+ return [state, setStateAction];
34
+ };
35
+
36
+ const initialUser = {
37
+ user: null,
38
+ fromCache: false,
39
+ };
40
+ const initialSession = {
41
+ session: null,
42
+ fromCache: false,
43
+ };
44
+ const StytchContext = createContext({ isMounted: false });
45
+ const StytchUserContext = createContext(initialUser);
46
+ const StytchSessionContext = createContext(initialSession);
47
+ const useIsMounted__INTERNAL = () => useContext(StytchContext).isMounted;
48
+ const isUIClient = (client) => {
49
+ return client.mountLogin !== undefined;
50
+ };
51
+ /**
52
+ * Returns the active User.
53
+ * Check the fromCache property to determine if the user data is from persistent storage.
54
+ * @example
55
+ * const {user} = useStytchUser();
56
+ * return (<h1>Welcome, {user.name.first_name}</h1>);
57
+ * @returns A {@link SWRUser}
58
+ */
7
59
  const useStytchUser = () => {
8
- return React.useContext(StytchUserContext);
60
+ invariant(useIsMounted__INTERNAL(), noProviderError('useStytchUser'));
61
+ return useContext(StytchUserContext);
9
62
  };
63
+ /**
64
+ * Returns the active user's Stytch session.
65
+ * @example
66
+ * const {session} = useStytchSession();
67
+ * useEffect(() => {
68
+ * if (!session) {
69
+ * router.replace('/login')
70
+ * }
71
+ * }, [session]);
72
+ * @returns A {@link SWRSession}
73
+ */
10
74
  const useStytchSession = () => {
11
- return React.useContext(StytchSessionContext);
75
+ invariant(useIsMounted__INTERNAL(), noProviderError('useStytchSession'));
76
+ return useContext(StytchSessionContext);
12
77
  };
78
+ /**
79
+ * Returns the Stytch client stored in the Stytch context.
80
+ *
81
+ * @example
82
+ * const stytch = useStytch();
83
+ * useEffect(() => {
84
+ * stytch.magicLinks.authenticate('...')
85
+ * }, [stytch]);
86
+ */
13
87
  const useStytch = () => {
14
- return React.useContext(StytchContext);
88
+ const ctx = useContext(StytchContext);
89
+ invariant(ctx.isMounted, noProviderError('useStytch'));
90
+ return ctx.client;
15
91
  };
16
92
  const withStytch = (Component) => {
17
93
  const WithStytch = (props) => {
18
- return React.createElement(Component, { ...props, stytch: useStytch() });
94
+ invariant(useIsMounted__INTERNAL(), noProviderError('withStytch'));
95
+ return React.createElement(Component, Object.assign({}, props, { stytch: useStytch() }));
19
96
  };
20
97
  WithStytch.displayName = `withStytch(${Component.displayName || Component.name || 'Component'})`;
21
98
  return WithStytch;
22
99
  };
23
100
  const withStytchUser = (Component) => {
24
101
  const WithStytchUser = (props) => {
25
- return React.createElement(Component, { ...props, stytchUser: useStytchUser() });
102
+ invariant(useIsMounted__INTERNAL(), noProviderError('withStytchUser'));
103
+ const { user, fromCache } = useStytchUser();
104
+ return React.createElement(Component, Object.assign({}, props, { stytchUser: user, stytchUserIsFromCache: fromCache }));
26
105
  };
27
106
  WithStytchUser.displayName = `withStytchUser(${Component.displayName || Component.name || 'Component'})`;
28
107
  return WithStytchUser;
29
108
  };
30
109
  const withStytchSession = (Component) => {
31
110
  const WithStytchSession = (props) => {
32
- return React.createElement(Component, { ...props, stytchSession: useStytchSession() });
111
+ invariant(useIsMounted__INTERNAL(), noProviderError('withStytchSession'));
112
+ const { session, fromCache } = useStytchSession();
113
+ return React.createElement(Component, Object.assign({}, props, { stytchSession: session, stytchSessionIsFromCache: fromCache }));
33
114
  };
34
115
  WithStytchSession.displayName = `withStytchSession(${Component.displayName || Component.name || 'Component'})`;
35
116
  return WithStytchSession;
36
117
  };
118
+ /**
119
+ * The Stytch Context Provider.
120
+ * Wrap your application with this component in the root file in order to use Stytch everywhere in your app.
121
+ * @example
122
+ * const stytch = new StytchHeadlessClient('public-token-<find yours in the stytch dashboard>')
123
+ *
124
+ * ReactDOM.render(
125
+ * <StytchProvider stytch={stytch}>
126
+ * <App />
127
+ * </StytchProvider>,
128
+ * document.getElementById('root'),
129
+ * )
130
+ */
37
131
  const StytchProvider = ({ stytch, children }) => {
38
- const [user, setUser] = React.useState(null);
39
- const [session, setSession] = React.useState(null);
40
- React.useEffect(() => {
41
- const unsubscribeUser = stytch.user.onChange((user) => setUser(user));
42
- const unsubscribeSession = stytch.session.onChange((session) => setSession(session));
132
+ invariant(!useIsMounted__INTERNAL(), providerMustBeUniqueError);
133
+ invariant(typeof window !== 'undefined', noSSRError);
134
+ const ctx = useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
135
+ const [user, setUser] = useAsyncState({
136
+ user: stytch.user.getSync(),
137
+ fromCache: true,
138
+ });
139
+ const [session, setSession] = useAsyncState({
140
+ session: stytch.session.getSync(),
141
+ fromCache: true,
142
+ });
143
+ useEffect(() => {
144
+ const unsubscribeUser = stytch.user.onChange((user) => setUser({
145
+ user,
146
+ fromCache: false,
147
+ }));
148
+ const unsubscribeSession = stytch.session.onChange((session) => setSession({
149
+ session,
150
+ fromCache: false,
151
+ }));
43
152
  return () => {
44
153
  unsubscribeUser();
45
154
  unsubscribeSession();
46
155
  };
47
156
  }, [stytch, setUser, setSession]);
48
- return (React.createElement(StytchContext.Provider, { value: stytch },
49
- React.createElement(StytchUserContext.Provider, { value: session && user },
50
- React.createElement(StytchSessionContext.Provider, { value: user && session }, children))));
157
+ const finalSess = !!session.session === !!user.user ? session : initialSession;
158
+ const finalUser = !!session.session === !!user.user ? user : initialUser;
159
+ return (React.createElement(StytchContext.Provider, { value: ctx },
160
+ React.createElement(StytchUserContext.Provider, { value: finalUser },
161
+ React.createElement(StytchSessionContext.Provider, { value: finalSess }, children))));
51
162
  };
52
163
 
53
164
  /**
54
- * Returns a unique element ID.
55
- * Client-side only - will return null for server-side
56
- * resolves: https://stytch.slack.com/archives/C015UDB4X33/p1641450131000800
57
- * based on: https://github.com/vercel/next.js/issues/7322#issuecomment-968858477
58
- */
59
- const useUniqueElementId = () => {
60
- const [elementId, setElementId] = React.useState(null);
61
- React.useEffect(() => {
62
- const randId = Math.floor(Math.random() * 1e6);
63
- setElementId(`stytch-magic-link-${randId}`);
64
- }, []);
65
- return elementId;
66
- };
67
- /**
68
- * Stytch JS React Component
165
+ * The Stytch Login Screen component.
166
+ * This component can only be used with a {@link StytchUIClient} client constructor
167
+ * passed into the {@link StytchProvider}
168
+ *
169
+ * See the {@link https://stytch.com/docs/sdks/javascript-sdk online reference}
170
+ * and {@link https://storybook.stytch.com interactive examples} for more.
69
171
  *
70
- * [Documentation](https://stytch.com/docs/javascript-sdk)
172
+ * @example
173
+ * <StytchLogin
174
+ * config={{
175
+ * products: ['emailMagicLinks', 'oauth'],
176
+ * emailMagicLinksOptions: {
177
+ * loginRedirectURL: 'https://example.com/authenticate',
178
+ * signupRedirectURL: 'https://example.com/authenticate',
179
+ * },
180
+ * oauthOptions: {
181
+ * providers: [{ type: OAuthProviders.Google }, { type: OAuthProviders.Microsoft }],
182
+ * },
183
+ * }}
184
+ * styles={{
185
+ * fontFamily: '"Helvetica New", Helvetica, sans-serif',
186
+ * primaryColor: '#0577CA',
187
+ * width: '321px',
188
+ * }}
189
+ * callbacks={{
190
+ * onEvent: (event) => console.log(event)
191
+ * }}
192
+ * />
193
+ * @param props {@link StytchProps}
71
194
  */
72
- const Stytch = ({ config, styles }) => {
195
+ const StytchLogin = ({ config, styles, callbacks }) => {
196
+ invariant(useIsMounted__INTERNAL(), noProviderError('<StytchLogin />'));
73
197
  const stytchClient = useStytch();
74
- const elementId = useUniqueElementId();
75
- React.useEffect(() => {
76
- if (!stytchClient || !elementId) {
198
+ const containerEl = useRef(null);
199
+ useLayoutEffect(() => {
200
+ if (!isUIClient(stytchClient)) {
201
+ throw Error(noHeadlessClientError);
202
+ }
203
+ if (!containerEl.current) {
77
204
  return;
78
205
  }
79
- if (!(stytchClient instanceof StytchUIClient)) {
80
- throw Error(`Tried to create a Stytch Login UI element using the Stytch Headless SDK.
81
- You must use the UI SDK to use UI elements.
82
- Please make sure you are importing from @stytch/vanilla-js and not from the @stytch/vanilla-js/headless.
83
- `);
206
+ if (containerEl.current.id) {
207
+ return;
84
208
  }
85
- stytchClient.mount({
209
+ const randId = Math.floor(Math.random() * 1e6);
210
+ const elementId = `stytch-magic-link-${randId}`;
211
+ containerEl.current.id = elementId;
212
+ stytchClient.mountLogin({
86
213
  config,
214
+ callbacks,
87
215
  elementId: `#${elementId}`,
88
216
  styles,
89
217
  });
90
- }, [elementId, stytchClient]);
91
- return elementId ? React.createElement("div", { id: elementId }) : null;
218
+ }, [stytchClient]);
219
+ return React.createElement("div", { ref: containerEl });
92
220
  };
93
221
 
94
- export { Stytch, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
222
+ export { StytchLogin, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
package/dist/index.js CHANGED
@@ -3,119 +3,231 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
- var vanillaJs = require('@stytch/vanilla-js');
7
6
 
8
- function _interopNamespace(e) {
9
- if (e && e.__esModule) return e;
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
20
- });
21
- }
22
- n["default"] = e;
23
- return Object.freeze(n);
7
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
10
+
11
+ const noProviderError = (item) => `${item} can only be used inside <StytchProvider>.`;
12
+ const providerMustBeUniqueError = 'You cannot render a <StytchProvider> inside another <StytchProvider>.';
13
+ const noSSRError = `The @stytch/react library is not meant for use with serverside environments like NextJS.
14
+ Use the @stytch/nextjs library instead -
15
+ npm remove @stytch/react && npm install @stytch/nextjs
16
+ `;
17
+ const noHeadlessClientError = `Tried to create a Stytch Login UI element using the Stytch Headless SDK.
18
+ You must use the UI SDK to use UI elements.
19
+ Please make sure you are importing from @stytch/vanilla-js and not from the @stytch/vanilla-js/headless.`;
20
+
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ function invariant(cond, message) {
23
+ if (!cond)
24
+ throw new Error(message);
24
25
  }
25
26
 
26
- var React__namespace = /*#__PURE__*/_interopNamespace(React);
27
+ // useState can cause memory leaks if it is set after the component unmounted. For example, if it is
28
+ // set after `await`, or in a `then`, `catch`, or `finally`, or in a setTimout/setInterval.
29
+ const useAsyncState = (initialState) => {
30
+ const isMounted = React.useRef(true);
31
+ const [state, setState] = React.useState(initialState);
32
+ React.useEffect(() => {
33
+ isMounted.current = true;
34
+ return () => {
35
+ isMounted.current = false;
36
+ };
37
+ }, []);
38
+ const setStateAction = React.useCallback((newState) => {
39
+ isMounted.current && setState(newState);
40
+ }, []);
41
+ return [state, setStateAction];
42
+ };
27
43
 
28
- const StytchUserContext = React__namespace.createContext(null);
29
- const StytchSessionContext = React__namespace.createContext(null);
30
- const StytchContext = React__namespace.createContext({});
44
+ const initialUser = {
45
+ user: null,
46
+ fromCache: false,
47
+ };
48
+ const initialSession = {
49
+ session: null,
50
+ fromCache: false,
51
+ };
52
+ const StytchContext = React.createContext({ isMounted: false });
53
+ const StytchUserContext = React.createContext(initialUser);
54
+ const StytchSessionContext = React.createContext(initialSession);
55
+ const useIsMounted__INTERNAL = () => React.useContext(StytchContext).isMounted;
56
+ const isUIClient = (client) => {
57
+ return client.mountLogin !== undefined;
58
+ };
59
+ /**
60
+ * Returns the active User.
61
+ * Check the fromCache property to determine if the user data is from persistent storage.
62
+ * @example
63
+ * const {user} = useStytchUser();
64
+ * return (<h1>Welcome, {user.name.first_name}</h1>);
65
+ * @returns A {@link SWRUser}
66
+ */
31
67
  const useStytchUser = () => {
32
- return React__namespace.useContext(StytchUserContext);
68
+ invariant(useIsMounted__INTERNAL(), noProviderError('useStytchUser'));
69
+ return React.useContext(StytchUserContext);
33
70
  };
71
+ /**
72
+ * Returns the active user's Stytch session.
73
+ * @example
74
+ * const {session} = useStytchSession();
75
+ * useEffect(() => {
76
+ * if (!session) {
77
+ * router.replace('/login')
78
+ * }
79
+ * }, [session]);
80
+ * @returns A {@link SWRSession}
81
+ */
34
82
  const useStytchSession = () => {
35
- return React__namespace.useContext(StytchSessionContext);
83
+ invariant(useIsMounted__INTERNAL(), noProviderError('useStytchSession'));
84
+ return React.useContext(StytchSessionContext);
36
85
  };
86
+ /**
87
+ * Returns the Stytch client stored in the Stytch context.
88
+ *
89
+ * @example
90
+ * const stytch = useStytch();
91
+ * useEffect(() => {
92
+ * stytch.magicLinks.authenticate('...')
93
+ * }, [stytch]);
94
+ */
37
95
  const useStytch = () => {
38
- return React__namespace.useContext(StytchContext);
96
+ const ctx = React.useContext(StytchContext);
97
+ invariant(ctx.isMounted, noProviderError('useStytch'));
98
+ return ctx.client;
39
99
  };
40
100
  const withStytch = (Component) => {
41
101
  const WithStytch = (props) => {
42
- return React__namespace.createElement(Component, { ...props, stytch: useStytch() });
102
+ invariant(useIsMounted__INTERNAL(), noProviderError('withStytch'));
103
+ return React__default["default"].createElement(Component, Object.assign({}, props, { stytch: useStytch() }));
43
104
  };
44
105
  WithStytch.displayName = `withStytch(${Component.displayName || Component.name || 'Component'})`;
45
106
  return WithStytch;
46
107
  };
47
108
  const withStytchUser = (Component) => {
48
109
  const WithStytchUser = (props) => {
49
- return React__namespace.createElement(Component, { ...props, stytchUser: useStytchUser() });
110
+ invariant(useIsMounted__INTERNAL(), noProviderError('withStytchUser'));
111
+ const { user, fromCache } = useStytchUser();
112
+ return React__default["default"].createElement(Component, Object.assign({}, props, { stytchUser: user, stytchUserIsFromCache: fromCache }));
50
113
  };
51
114
  WithStytchUser.displayName = `withStytchUser(${Component.displayName || Component.name || 'Component'})`;
52
115
  return WithStytchUser;
53
116
  };
54
117
  const withStytchSession = (Component) => {
55
118
  const WithStytchSession = (props) => {
56
- return React__namespace.createElement(Component, { ...props, stytchSession: useStytchSession() });
119
+ invariant(useIsMounted__INTERNAL(), noProviderError('withStytchSession'));
120
+ const { session, fromCache } = useStytchSession();
121
+ return React__default["default"].createElement(Component, Object.assign({}, props, { stytchSession: session, stytchSessionIsFromCache: fromCache }));
57
122
  };
58
123
  WithStytchSession.displayName = `withStytchSession(${Component.displayName || Component.name || 'Component'})`;
59
124
  return WithStytchSession;
60
125
  };
126
+ /**
127
+ * The Stytch Context Provider.
128
+ * Wrap your application with this component in the root file in order to use Stytch everywhere in your app.
129
+ * @example
130
+ * const stytch = new StytchHeadlessClient('public-token-<find yours in the stytch dashboard>')
131
+ *
132
+ * ReactDOM.render(
133
+ * <StytchProvider stytch={stytch}>
134
+ * <App />
135
+ * </StytchProvider>,
136
+ * document.getElementById('root'),
137
+ * )
138
+ */
61
139
  const StytchProvider = ({ stytch, children }) => {
62
- const [user, setUser] = React__namespace.useState(null);
63
- const [session, setSession] = React__namespace.useState(null);
64
- React__namespace.useEffect(() => {
65
- const unsubscribeUser = stytch.user.onChange((user) => setUser(user));
66
- const unsubscribeSession = stytch.session.onChange((session) => setSession(session));
140
+ invariant(!useIsMounted__INTERNAL(), providerMustBeUniqueError);
141
+ invariant(typeof window !== 'undefined', noSSRError);
142
+ const ctx = React.useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
143
+ const [user, setUser] = useAsyncState({
144
+ user: stytch.user.getSync(),
145
+ fromCache: true,
146
+ });
147
+ const [session, setSession] = useAsyncState({
148
+ session: stytch.session.getSync(),
149
+ fromCache: true,
150
+ });
151
+ React.useEffect(() => {
152
+ const unsubscribeUser = stytch.user.onChange((user) => setUser({
153
+ user,
154
+ fromCache: false,
155
+ }));
156
+ const unsubscribeSession = stytch.session.onChange((session) => setSession({
157
+ session,
158
+ fromCache: false,
159
+ }));
67
160
  return () => {
68
161
  unsubscribeUser();
69
162
  unsubscribeSession();
70
163
  };
71
164
  }, [stytch, setUser, setSession]);
72
- return (React__namespace.createElement(StytchContext.Provider, { value: stytch },
73
- React__namespace.createElement(StytchUserContext.Provider, { value: session && user },
74
- React__namespace.createElement(StytchSessionContext.Provider, { value: user && session }, children))));
165
+ const finalSess = !!session.session === !!user.user ? session : initialSession;
166
+ const finalUser = !!session.session === !!user.user ? user : initialUser;
167
+ return (React__default["default"].createElement(StytchContext.Provider, { value: ctx },
168
+ React__default["default"].createElement(StytchUserContext.Provider, { value: finalUser },
169
+ React__default["default"].createElement(StytchSessionContext.Provider, { value: finalSess }, children))));
75
170
  };
76
171
 
77
172
  /**
78
- * Returns a unique element ID.
79
- * Client-side only - will return null for server-side
80
- * resolves: https://stytch.slack.com/archives/C015UDB4X33/p1641450131000800
81
- * based on: https://github.com/vercel/next.js/issues/7322#issuecomment-968858477
82
- */
83
- const useUniqueElementId = () => {
84
- const [elementId, setElementId] = React__namespace.useState(null);
85
- React__namespace.useEffect(() => {
86
- const randId = Math.floor(Math.random() * 1e6);
87
- setElementId(`stytch-magic-link-${randId}`);
88
- }, []);
89
- return elementId;
90
- };
91
- /**
92
- * Stytch JS React Component
173
+ * The Stytch Login Screen component.
174
+ * This component can only be used with a {@link StytchUIClient} client constructor
175
+ * passed into the {@link StytchProvider}
176
+ *
177
+ * See the {@link https://stytch.com/docs/sdks/javascript-sdk online reference}
178
+ * and {@link https://storybook.stytch.com interactive examples} for more.
93
179
  *
94
- * [Documentation](https://stytch.com/docs/javascript-sdk)
180
+ * @example
181
+ * <StytchLogin
182
+ * config={{
183
+ * products: ['emailMagicLinks', 'oauth'],
184
+ * emailMagicLinksOptions: {
185
+ * loginRedirectURL: 'https://example.com/authenticate',
186
+ * signupRedirectURL: 'https://example.com/authenticate',
187
+ * },
188
+ * oauthOptions: {
189
+ * providers: [{ type: OAuthProviders.Google }, { type: OAuthProviders.Microsoft }],
190
+ * },
191
+ * }}
192
+ * styles={{
193
+ * fontFamily: '"Helvetica New", Helvetica, sans-serif',
194
+ * primaryColor: '#0577CA',
195
+ * width: '321px',
196
+ * }}
197
+ * callbacks={{
198
+ * onEvent: (event) => console.log(event)
199
+ * }}
200
+ * />
201
+ * @param props {@link StytchProps}
95
202
  */
96
- const Stytch = ({ config, styles }) => {
203
+ const StytchLogin = ({ config, styles, callbacks }) => {
204
+ invariant(useIsMounted__INTERNAL(), noProviderError('<StytchLogin />'));
97
205
  const stytchClient = useStytch();
98
- const elementId = useUniqueElementId();
99
- React__namespace.useEffect(() => {
100
- if (!stytchClient || !elementId) {
206
+ const containerEl = React.useRef(null);
207
+ React.useLayoutEffect(() => {
208
+ if (!isUIClient(stytchClient)) {
209
+ throw Error(noHeadlessClientError);
210
+ }
211
+ if (!containerEl.current) {
101
212
  return;
102
213
  }
103
- if (!(stytchClient instanceof vanillaJs.StytchUIClient)) {
104
- throw Error(`Tried to create a Stytch Login UI element using the Stytch Headless SDK.
105
- You must use the UI SDK to use UI elements.
106
- Please make sure you are importing from @stytch/vanilla-js and not from the @stytch/vanilla-js/headless.
107
- `);
214
+ if (containerEl.current.id) {
215
+ return;
108
216
  }
109
- stytchClient.mount({
217
+ const randId = Math.floor(Math.random() * 1e6);
218
+ const elementId = `stytch-magic-link-${randId}`;
219
+ containerEl.current.id = elementId;
220
+ stytchClient.mountLogin({
110
221
  config,
222
+ callbacks,
111
223
  elementId: `#${elementId}`,
112
224
  styles,
113
225
  });
114
- }, [elementId, stytchClient]);
115
- return elementId ? React__namespace.createElement("div", { id: elementId }) : null;
226
+ }, [stytchClient]);
227
+ return React__default["default"].createElement("div", { ref: containerEl });
116
228
  };
117
229
 
118
- exports.Stytch = Stytch;
230
+ exports.StytchLogin = StytchLogin;
119
231
  exports.StytchProvider = StytchProvider;
120
232
  exports.useStytch = useStytch;
121
233
  exports.useStytchSession = useStytchSession;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stytch/react",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Stytch's official React Library",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",
@@ -12,19 +12,32 @@
12
12
  "build": "npm run clean && npm run compile",
13
13
  "clean": "rm -rf ./dist",
14
14
  "compile": "rollup -c",
15
- "dev": "tsc -p tsconfig.build.json --watch"
15
+ "dev": "tsc -p tsconfig.build.json --watch",
16
+ "test": "jest"
16
17
  },
17
18
  "license": "MIT",
19
+ "homepage": "https://stytch.com/docs/sdks/javascript-sdk",
18
20
  "author": "Stytch",
21
+ "keywords": [
22
+ "stytch",
23
+ "react",
24
+ "nextjs",
25
+ "typescript",
26
+ "auth",
27
+ "authentication",
28
+ "session",
29
+ "jwt",
30
+ "user"
31
+ ],
19
32
  "devDependencies": {
20
33
  "@babel/runtime": "^7.18.6",
21
- "@stytch/vanilla-js": "0.0.1",
34
+ "@stytch/vanilla-js": "0.1.0",
22
35
  "eslint-config-custom": "0.0.0",
23
36
  "rollup": "^2.56.3",
24
- "typescript": "^4.5.3"
37
+ "typescript": "4.7.4"
25
38
  },
26
39
  "peerDependencies": {
27
- "@stytch/vanilla-js": "^0.0.1",
40
+ "@stytch/vanilla-js": "^0.1.0",
28
41
  "react": ">= 17.0.2",
29
42
  "react-dom": ">= 17.0.2"
30
43
  }