@stytch/react 0.0.3-0 → 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
@@ -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
 
@@ -54,7 +54,11 @@ const App = () => {
54
54
 
55
55
  return (
56
56
  <div id="login">
57
- <Stytch config={stytchProps.loginOrSignupView} styles={stytchProps.style} callbacks={stytchProps.callbacks} />
57
+ <StytchLogin
58
+ config={stytchProps.loginOrSignupView}
59
+ styles={stytchProps.style}
60
+ callbacks={stytchProps.callbacks}
61
+ />
58
62
  </div>
59
63
  );
60
64
  };
package/dist/index.d.ts CHANGED
@@ -1,35 +1,193 @@
1
1
  /// <reference types="react" />
2
- import { Callbacks, Config, StyleConfig, User, Session, StytchUIClient } from "@stytch/vanilla-js";
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";
3
5
  import React from "react";
4
6
  import { ReactNode } from "react";
5
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
+ }
6
72
  /**
7
- * 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}
8
76
  *
9
- * [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.
10
107
  */
11
- declare const Stytch: ({ config, styles, callbacks }: {
12
- config: Config;
13
- styles: StyleConfig;
14
- callbacks?: Callbacks | undefined;
15
- }) => JSX.Element | null;
16
108
  type StytchClient = StytchUIClient | StytchHeadlessClient;
17
- declare const useStytchUser: () => User | null;
18
- 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
+ */
19
159
  declare const useStytch: () => StytchClient;
20
160
  declare const withStytch: <T extends object>(Component: React.ComponentType<T & {
21
161
  stytch: StytchClient;
22
162
  }>) => React.ComponentType<T>;
23
163
  declare const withStytchUser: <T extends object>(Component: React.ComponentType<T & {
24
164
  stytchUser: User | null;
165
+ stytchUserIsFromCache: boolean;
25
166
  }>) => React.ComponentType<T>;
26
167
  declare const withStytchSession: <T extends object>(Component: React.ComponentType<T & {
27
168
  stytchSession: Session | null;
169
+ stytchSessionIsFromCache: boolean;
28
170
  }>) => React.ComponentType<T>;
29
171
  type StytchProviderProps = {
172
+ /**
173
+ * A Stytch client instance, either a {@link StytchUIClient} or {@link StytchHeadlessClient}
174
+ */
30
175
  stytch: StytchClient;
31
176
  children?: ReactNode;
32
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
+ */
33
191
  declare const StytchProvider: ({ stytch, children }: StytchProviderProps) => JSX.Element;
34
- export { Stytch, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
192
+ export { StytchLogin, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
35
193
  export type { StytchProviderProps };
@@ -1,35 +1,193 @@
1
1
  /// <reference types="react" />
2
- import { Callbacks, Config, StyleConfig, User, Session, StytchUIClient } from "@stytch/vanilla-js";
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";
3
5
  import React from "react";
4
6
  import { ReactNode } from "react";
5
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
+ }
6
72
  /**
7
- * 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}
8
76
  *
9
- * [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.
10
107
  */
11
- declare const Stytch: ({ config, styles, callbacks }: {
12
- config: Config;
13
- styles: StyleConfig;
14
- callbacks?: Callbacks | undefined;
15
- }) => JSX.Element | null;
16
108
  type StytchClient = StytchUIClient | StytchHeadlessClient;
17
- declare const useStytchUser: () => User | null;
18
- 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
+ */
19
159
  declare const useStytch: () => StytchClient;
20
160
  declare const withStytch: <T extends object>(Component: React.ComponentType<T & {
21
161
  stytch: StytchClient;
22
162
  }>) => React.ComponentType<T>;
23
163
  declare const withStytchUser: <T extends object>(Component: React.ComponentType<T & {
24
164
  stytchUser: User | null;
165
+ stytchUserIsFromCache: boolean;
25
166
  }>) => React.ComponentType<T>;
26
167
  declare const withStytchSession: <T extends object>(Component: React.ComponentType<T & {
27
168
  stytchSession: Session | null;
169
+ stytchSessionIsFromCache: boolean;
28
170
  }>) => React.ComponentType<T>;
29
171
  type StytchProviderProps = {
172
+ /**
173
+ * A Stytch client instance, either a {@link StytchUIClient} or {@link StytchHeadlessClient}
174
+ */
30
175
  stytch: StytchClient;
31
176
  children?: ReactNode;
32
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
+ */
33
191
  declare const StytchProvider: ({ stytch, children }: StytchProviderProps) => JSX.Element;
34
- export { Stytch, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
192
+ export { StytchLogin, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
35
193
  export type { StytchProviderProps };
package/dist/index.esm.js CHANGED
@@ -1,5 +1,4 @@
1
- import * as React from 'react';
2
- import React__default, { useRef, useState, useEffect, useCallback, createContext, useContext, useMemo } from 'react';
1
+ import React, { useRef, useState, useEffect, useCallback, createContext, useContext, useMemo, useLayoutEffect } from 'react';
3
2
 
4
3
  const noProviderError = (item) => `${item} can only be used inside <StytchProvider>.`;
5
4
  const providerMustBeUniqueError = 'You cannot render a <StytchProvider> inside another <StytchProvider>.';
@@ -34,21 +33,57 @@ const useAsyncState = (initialState) => {
34
33
  return [state, setStateAction];
35
34
  };
36
35
 
36
+ const initialUser = {
37
+ user: null,
38
+ fromCache: false,
39
+ };
40
+ const initialSession = {
41
+ session: null,
42
+ fromCache: false,
43
+ };
37
44
  const StytchContext = createContext({ isMounted: false });
38
- const StytchUserContext = createContext(null);
39
- const StytchSessionContext = createContext(null);
45
+ const StytchUserContext = createContext(initialUser);
46
+ const StytchSessionContext = createContext(initialSession);
40
47
  const useIsMounted__INTERNAL = () => useContext(StytchContext).isMounted;
41
48
  const isUIClient = (client) => {
42
- return client.mount !== undefined;
49
+ return client.mountLogin !== undefined;
43
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
+ */
44
59
  const useStytchUser = () => {
45
60
  invariant(useIsMounted__INTERNAL(), noProviderError('useStytchUser'));
46
61
  return useContext(StytchUserContext);
47
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
+ */
48
74
  const useStytchSession = () => {
49
75
  invariant(useIsMounted__INTERNAL(), noProviderError('useStytchSession'));
50
76
  return useContext(StytchSessionContext);
51
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
+ */
52
87
  const useStytch = () => {
53
88
  const ctx = useContext(StytchContext);
54
89
  invariant(ctx.isMounted, noProviderError('useStytch'));
@@ -57,7 +92,7 @@ const useStytch = () => {
57
92
  const withStytch = (Component) => {
58
93
  const WithStytch = (props) => {
59
94
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytch'));
60
- return React__default.createElement(Component, { ...props, stytch: useStytch() });
95
+ return React.createElement(Component, Object.assign({}, props, { stytch: useStytch() }));
61
96
  };
62
97
  WithStytch.displayName = `withStytch(${Component.displayName || Component.name || 'Component'})`;
63
98
  return WithStytch;
@@ -65,7 +100,8 @@ const withStytch = (Component) => {
65
100
  const withStytchUser = (Component) => {
66
101
  const WithStytchUser = (props) => {
67
102
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytchUser'));
68
- return React__default.createElement(Component, { ...props, stytchUser: useStytchUser() });
103
+ const { user, fromCache } = useStytchUser();
104
+ return React.createElement(Component, Object.assign({}, props, { stytchUser: user, stytchUserIsFromCache: fromCache }));
69
105
  };
70
106
  WithStytchUser.displayName = `withStytchUser(${Component.displayName || Component.name || 'Component'})`;
71
107
  return WithStytchUser;
@@ -73,65 +109,114 @@ const withStytchUser = (Component) => {
73
109
  const withStytchSession = (Component) => {
74
110
  const WithStytchSession = (props) => {
75
111
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytchSession'));
76
- return React__default.createElement(Component, { ...props, stytchSession: useStytchSession() });
112
+ const { session, fromCache } = useStytchSession();
113
+ return React.createElement(Component, Object.assign({}, props, { stytchSession: session, stytchSessionIsFromCache: fromCache }));
77
114
  };
78
115
  WithStytchSession.displayName = `withStytchSession(${Component.displayName || Component.name || 'Component'})`;
79
116
  return WithStytchSession;
80
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
+ */
81
131
  const StytchProvider = ({ stytch, children }) => {
82
132
  invariant(!useIsMounted__INTERNAL(), providerMustBeUniqueError);
83
133
  invariant(typeof window !== 'undefined', noSSRError);
84
134
  const ctx = useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
85
- const [user, setUser] = useAsyncState(stytch.user.getSync());
86
- const [session, setSession] = useAsyncState(stytch.session.getSync());
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
+ });
87
143
  useEffect(() => {
88
- const unsubscribeUser = stytch.user.onChange((user) => setUser(user));
89
- const unsubscribeSession = stytch.session.onChange((session) => setSession(session));
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
+ }));
90
152
  return () => {
91
153
  unsubscribeUser();
92
154
  unsubscribeSession();
93
155
  };
94
156
  }, [stytch, setUser, setSession]);
95
- return (React__default.createElement(StytchContext.Provider, { value: ctx },
96
- React__default.createElement(StytchUserContext.Provider, { value: session && user },
97
- React__default.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))));
98
162
  };
99
163
 
100
164
  /**
101
- * Returns a unique element ID.
102
- * Intended to only be used client-side - this will cause HTML mismatch issues in SSR!
103
- * cf: https://github.com/vercel/next.js/issues/7322#issuecomment-968858477
104
- */
105
- const useUniqueElementId = () => {
106
- return React.useMemo(() => {
107
- const randId = Math.floor(Math.random() * 1e6);
108
- return `stytch-magic-link-${randId}`;
109
- }, []);
110
- };
111
- /**
112
- * 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.
113
171
  *
114
- * [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}
115
194
  */
116
- const Stytch = ({ config, styles, callbacks, }) => {
117
- invariant(useIsMounted__INTERNAL(), noProviderError('<Stytch />'));
195
+ const StytchLogin = ({ config, styles, callbacks }) => {
196
+ invariant(useIsMounted__INTERNAL(), noProviderError('<StytchLogin />'));
118
197
  const stytchClient = useStytch();
119
- const elementId = useUniqueElementId();
120
- React.useEffect(() => {
121
- if (!elementId) {
122
- return;
123
- }
198
+ const containerEl = useRef(null);
199
+ useLayoutEffect(() => {
124
200
  if (!isUIClient(stytchClient)) {
125
201
  throw Error(noHeadlessClientError);
126
202
  }
127
- stytchClient.mount({
203
+ if (!containerEl.current) {
204
+ return;
205
+ }
206
+ if (containerEl.current.id) {
207
+ return;
208
+ }
209
+ const randId = Math.floor(Math.random() * 1e6);
210
+ const elementId = `stytch-magic-link-${randId}`;
211
+ containerEl.current.id = elementId;
212
+ stytchClient.mountLogin({
128
213
  config,
129
214
  callbacks,
130
215
  elementId: `#${elementId}`,
131
216
  styles,
132
217
  });
133
- }, [elementId, stytchClient]);
134
- return elementId ? React.createElement("div", { id: elementId }) : null;
218
+ }, [stytchClient]);
219
+ return React.createElement("div", { ref: containerEl });
135
220
  };
136
221
 
137
- export { Stytch, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
222
+ export { StytchLogin, StytchProvider, useStytch, useStytchSession, useStytchUser, withStytch, withStytchSession, withStytchUser };
package/dist/index.js CHANGED
@@ -6,26 +6,7 @@ var React = require('react');
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
9
- function _interopNamespace(e) {
10
- if (e && e.__esModule) return e;
11
- var n = Object.create(null);
12
- if (e) {
13
- Object.keys(e).forEach(function (k) {
14
- if (k !== 'default') {
15
- var d = Object.getOwnPropertyDescriptor(e, k);
16
- Object.defineProperty(n, k, d.get ? d : {
17
- enumerable: true,
18
- get: function () { return e[k]; }
19
- });
20
- }
21
- });
22
- }
23
- n["default"] = e;
24
- return Object.freeze(n);
25
- }
26
-
27
9
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
28
- var React__namespace = /*#__PURE__*/_interopNamespace(React);
29
10
 
30
11
  const noProviderError = (item) => `${item} can only be used inside <StytchProvider>.`;
31
12
  const providerMustBeUniqueError = 'You cannot render a <StytchProvider> inside another <StytchProvider>.';
@@ -60,21 +41,57 @@ const useAsyncState = (initialState) => {
60
41
  return [state, setStateAction];
61
42
  };
62
43
 
44
+ const initialUser = {
45
+ user: null,
46
+ fromCache: false,
47
+ };
48
+ const initialSession = {
49
+ session: null,
50
+ fromCache: false,
51
+ };
63
52
  const StytchContext = React.createContext({ isMounted: false });
64
- const StytchUserContext = React.createContext(null);
65
- const StytchSessionContext = React.createContext(null);
53
+ const StytchUserContext = React.createContext(initialUser);
54
+ const StytchSessionContext = React.createContext(initialSession);
66
55
  const useIsMounted__INTERNAL = () => React.useContext(StytchContext).isMounted;
67
56
  const isUIClient = (client) => {
68
- return client.mount !== undefined;
57
+ return client.mountLogin !== undefined;
69
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
+ */
70
67
  const useStytchUser = () => {
71
68
  invariant(useIsMounted__INTERNAL(), noProviderError('useStytchUser'));
72
69
  return React.useContext(StytchUserContext);
73
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
+ */
74
82
  const useStytchSession = () => {
75
83
  invariant(useIsMounted__INTERNAL(), noProviderError('useStytchSession'));
76
84
  return React.useContext(StytchSessionContext);
77
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
+ */
78
95
  const useStytch = () => {
79
96
  const ctx = React.useContext(StytchContext);
80
97
  invariant(ctx.isMounted, noProviderError('useStytch'));
@@ -83,7 +100,7 @@ const useStytch = () => {
83
100
  const withStytch = (Component) => {
84
101
  const WithStytch = (props) => {
85
102
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytch'));
86
- return React__default["default"].createElement(Component, { ...props, stytch: useStytch() });
103
+ return React__default["default"].createElement(Component, Object.assign({}, props, { stytch: useStytch() }));
87
104
  };
88
105
  WithStytch.displayName = `withStytch(${Component.displayName || Component.name || 'Component'})`;
89
106
  return WithStytch;
@@ -91,7 +108,8 @@ const withStytch = (Component) => {
91
108
  const withStytchUser = (Component) => {
92
109
  const WithStytchUser = (props) => {
93
110
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytchUser'));
94
- return React__default["default"].createElement(Component, { ...props, stytchUser: useStytchUser() });
111
+ const { user, fromCache } = useStytchUser();
112
+ return React__default["default"].createElement(Component, Object.assign({}, props, { stytchUser: user, stytchUserIsFromCache: fromCache }));
95
113
  };
96
114
  WithStytchUser.displayName = `withStytchUser(${Component.displayName || Component.name || 'Component'})`;
97
115
  return WithStytchUser;
@@ -99,68 +117,117 @@ const withStytchUser = (Component) => {
99
117
  const withStytchSession = (Component) => {
100
118
  const WithStytchSession = (props) => {
101
119
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytchSession'));
102
- return React__default["default"].createElement(Component, { ...props, stytchSession: useStytchSession() });
120
+ const { session, fromCache } = useStytchSession();
121
+ return React__default["default"].createElement(Component, Object.assign({}, props, { stytchSession: session, stytchSessionIsFromCache: fromCache }));
103
122
  };
104
123
  WithStytchSession.displayName = `withStytchSession(${Component.displayName || Component.name || 'Component'})`;
105
124
  return WithStytchSession;
106
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
+ */
107
139
  const StytchProvider = ({ stytch, children }) => {
108
140
  invariant(!useIsMounted__INTERNAL(), providerMustBeUniqueError);
109
141
  invariant(typeof window !== 'undefined', noSSRError);
110
142
  const ctx = React.useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
111
- const [user, setUser] = useAsyncState(stytch.user.getSync());
112
- const [session, setSession] = useAsyncState(stytch.session.getSync());
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
+ });
113
151
  React.useEffect(() => {
114
- const unsubscribeUser = stytch.user.onChange((user) => setUser(user));
115
- const unsubscribeSession = stytch.session.onChange((session) => setSession(session));
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
+ }));
116
160
  return () => {
117
161
  unsubscribeUser();
118
162
  unsubscribeSession();
119
163
  };
120
164
  }, [stytch, setUser, setSession]);
165
+ const finalSess = !!session.session === !!user.user ? session : initialSession;
166
+ const finalUser = !!session.session === !!user.user ? user : initialUser;
121
167
  return (React__default["default"].createElement(StytchContext.Provider, { value: ctx },
122
- React__default["default"].createElement(StytchUserContext.Provider, { value: session && user },
123
- React__default["default"].createElement(StytchSessionContext.Provider, { value: user && session }, children))));
168
+ React__default["default"].createElement(StytchUserContext.Provider, { value: finalUser },
169
+ React__default["default"].createElement(StytchSessionContext.Provider, { value: finalSess }, children))));
124
170
  };
125
171
 
126
172
  /**
127
- * Returns a unique element ID.
128
- * Intended to only be used client-side - this will cause HTML mismatch issues in SSR!
129
- * cf: https://github.com/vercel/next.js/issues/7322#issuecomment-968858477
130
- */
131
- const useUniqueElementId = () => {
132
- return React__namespace.useMemo(() => {
133
- const randId = Math.floor(Math.random() * 1e6);
134
- return `stytch-magic-link-${randId}`;
135
- }, []);
136
- };
137
- /**
138
- * 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.
139
179
  *
140
- * [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}
141
202
  */
142
- const Stytch = ({ config, styles, callbacks, }) => {
143
- invariant(useIsMounted__INTERNAL(), noProviderError('<Stytch />'));
203
+ const StytchLogin = ({ config, styles, callbacks }) => {
204
+ invariant(useIsMounted__INTERNAL(), noProviderError('<StytchLogin />'));
144
205
  const stytchClient = useStytch();
145
- const elementId = useUniqueElementId();
146
- React__namespace.useEffect(() => {
147
- if (!elementId) {
148
- return;
149
- }
206
+ const containerEl = React.useRef(null);
207
+ React.useLayoutEffect(() => {
150
208
  if (!isUIClient(stytchClient)) {
151
209
  throw Error(noHeadlessClientError);
152
210
  }
153
- stytchClient.mount({
211
+ if (!containerEl.current) {
212
+ return;
213
+ }
214
+ if (containerEl.current.id) {
215
+ return;
216
+ }
217
+ const randId = Math.floor(Math.random() * 1e6);
218
+ const elementId = `stytch-magic-link-${randId}`;
219
+ containerEl.current.id = elementId;
220
+ stytchClient.mountLogin({
154
221
  config,
155
222
  callbacks,
156
223
  elementId: `#${elementId}`,
157
224
  styles,
158
225
  });
159
- }, [elementId, stytchClient]);
160
- return elementId ? React__namespace.createElement("div", { id: elementId }) : null;
226
+ }, [stytchClient]);
227
+ return React__default["default"].createElement("div", { ref: containerEl });
161
228
  };
162
229
 
163
- exports.Stytch = Stytch;
230
+ exports.StytchLogin = StytchLogin;
164
231
  exports.StytchProvider = StytchProvider;
165
232
  exports.useStytch = useStytch;
166
233
  exports.useStytchSession = useStytchSession;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stytch/react",
3
- "version": "0.0.3-0",
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",
@@ -16,18 +16,29 @@
16
16
  "test": "jest"
17
17
  },
18
18
  "license": "MIT",
19
+ "homepage": "https://stytch.com/docs/sdks/javascript-sdk",
19
20
  "author": "Stytch",
21
+ "keywords": [
22
+ "stytch",
23
+ "react",
24
+ "nextjs",
25
+ "typescript",
26
+ "auth",
27
+ "authentication",
28
+ "session",
29
+ "jwt",
30
+ "user"
31
+ ],
20
32
  "devDependencies": {
21
33
  "@babel/runtime": "^7.18.6",
22
- "@stytch/vanilla-js": "0.0.2-0",
34
+ "@stytch/vanilla-js": "0.1.0",
23
35
  "eslint-config-custom": "0.0.0",
24
36
  "rollup": "^2.56.3",
25
- "typescript": "^4.5.3"
37
+ "typescript": "4.7.4"
26
38
  },
27
39
  "peerDependencies": {
28
- "@stytch/vanilla-js": "^0.0.2-0",
40
+ "@stytch/vanilla-js": "^0.1.0",
29
41
  "react": ">= 17.0.2",
30
42
  "react-dom": ">= 17.0.2"
31
- },
32
- "stableVersion": "0.0.2"
43
+ }
33
44
  }