Skip to content

Commit 93ce84f

Browse files
committed
Add error hook and handler
1 parent e3c837e commit 93ce84f

File tree

8 files changed

+91
-6
lines changed

8 files changed

+91
-6
lines changed

src/asyncWithLDProvider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ import { camelCaseKeys, fetchFlags, getFlattenedFlagsFromChangeset } from './uti
3232
export default async function asyncWithLDProvider(config: AsyncProviderConfig) {
3333
const { clientSideID, user, flags: targetFlags, options, reactOptions: userReactOptions } = config;
3434
const reactOptions = { ...defaultReactOptions, ...userReactOptions };
35-
const { ldClient } = await initLDClient(clientSideID, user, reactOptions, options, targetFlags);
35+
const { ldClient, error } = await initLDClient(clientSideID, user, reactOptions, options, targetFlags);
3636

3737
const LDProvider = ({ children }: { children: ReactNode }) => {
3838
const [ldData, setLDData] = useState({
3939
flags: fetchFlags(ldClient, reactOptions, targetFlags),
4040
ldClient,
41+
error,
4142
});
4243

4344
useEffect(() => {

src/context.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ interface LDContext {
1818
* @see https://docs.launchdarkly.com/sdk/client-side/javascript
1919
*/
2020
ldClient?: LDClient;
21+
22+
/**
23+
* LaunchDarkly client initialization error, if there was one.
24+
*/
25+
error?: Error;
2126
}
2227

2328
/**

src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,18 @@ import asyncWithLDProvider from './asyncWithLDProvider';
44
import withLDConsumer from './withLDConsumer';
55
import useFlags from './useFlags';
66
import useLDClient from './useLDClient';
7+
import useLDClientError from './useLDClientError';
78
import { camelCaseKeys } from './utils';
89

910
export * from './types';
1011

11-
export { LDProvider, asyncWithLDProvider, camelCaseKeys, useFlags, useLDClient, withLDProvider, withLDConsumer };
12+
export {
13+
LDProvider,
14+
asyncWithLDProvider,
15+
camelCaseKeys,
16+
useFlags,
17+
useLDClient,
18+
useLDClientError,
19+
withLDProvider,
20+
withLDConsumer,
21+
};

src/initLDClient.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ describe('initLDClient', () => {
2727
beforeEach(() => {
2828
mockLDClient = {
2929
on: (e: string, cb: () => void) => {
30-
cb();
30+
if (e === 'ready') {
31+
cb();
32+
}
3133
},
34+
off: jest.fn(),
3235
allFlags: () => flags,
3336
variation: jest.fn(() => true),
3437
};
@@ -89,4 +92,31 @@ describe('initLDClient', () => {
8992
expect(mockLDClient.variation).toHaveBeenNthCalledWith(2, 'lonelier-flag', false);
9093
expect(flagsClient).toEqual({ flags: { lonelyFlag: true, lonelierFlag: true }, ldClient: mockLDClient });
9194
});
95+
96+
test('returns an error', async () => {
97+
const error = new Error('Out of cheese');
98+
mockLDClient.on = (e: string, cb: (err: Error) => void) => {
99+
if (e === 'failed') {
100+
cb(error);
101+
}
102+
};
103+
104+
const flagsClient = await initLDClient(clientSideID);
105+
106+
expect(flagsClient).toEqual({ flags: {}, ldClient: mockLDClient, error });
107+
});
108+
109+
test('executes error handler', async () => {
110+
const error = new Error('Out of cheese');
111+
mockLDClient.on = (e: string, cb: (err: Error) => void) => {
112+
if (e === 'failed') {
113+
cb(error);
114+
}
115+
};
116+
const clientInitializationErrorHandler = jest.fn();
117+
118+
await initLDClient(clientSideID, undefined, { clientInitializationErrorHandler });
119+
120+
expect(clientInitializationErrorHandler).toHaveBeenCalledWith(error);
121+
});
92122
});

src/initLDClient.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,24 @@ const initLDClient = async (
2626
const ldClient = ldClientInitialize(clientSideID, user, allOptions);
2727

2828
return new Promise<AllFlagsLDClient>((resolve) => {
29-
ldClient.on('ready', () => {
29+
function cleanup() {
30+
ldClient.off('ready', handleReady);
31+
ldClient.off('failed', handleFailure);
32+
}
33+
function handleFailure(error: Error) {
34+
cleanup();
35+
if (reactOptions.clientInitializationErrorHandler) {
36+
reactOptions.clientInitializationErrorHandler(error);
37+
}
38+
resolve({ flags: {}, ldClient, error });
39+
}
40+
function handleReady() {
41+
cleanup();
3042
const flags = fetchFlags(ldClient, reactOptions, targetFlags);
3143
resolve({ flags, ldClient });
32-
});
44+
}
45+
ldClient.on('failed', handleFailure);
46+
ldClient.on('ready', handleReady);
3347
});
3448
};
3549

src/provider.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,16 @@ class LDProvider extends Component<PropsWithChildren<ProviderConfig>, HocState>
6565
let ldClient = await this.props.ldClient;
6666
const reactOptions = this.getReactOptions();
6767
let fetchedFlags;
68+
let error: Error | undefined;
6869
if (ldClient) {
6970
fetchedFlags = fetchFlags(ldClient, reactOptions, flags);
7071
} else {
7172
const initialisedOutput = await initLDClient(clientSideID, user, reactOptions, options, flags);
7273
fetchedFlags = initialisedOutput.flags;
7374
ldClient = initialisedOutput.ldClient;
75+
error = initialisedOutput.error;
7476
}
75-
this.setState({ flags: fetchedFlags, ldClient });
77+
this.setState({ flags: fetchedFlags, ldClient, error });
7678
this.subscribeToChanges(ldClient);
7779
};
7880

src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export interface LDReactOptions {
2121
* @see https://docs.launchdarkly.com/sdk/client-side/react/react-web#flag-keys
2222
*/
2323
useCamelCaseFlagKeys?: boolean;
24+
25+
/**
26+
* Callback to pass a LaunchDarkly initialization error to, if one occurs.
27+
*/
28+
clientInitializationErrorHandler?(error: Error): void;
2429
}
2530

2631
/**
@@ -121,4 +126,9 @@ export interface AllFlagsLDClient {
121126
* @see https://docs.launchdarkly.com/sdk/client-side/javascript
122127
*/
123128
ldClient: LDClient;
129+
130+
/**
131+
* LaunchDarkly client initialization error, if there was one.
132+
*/
133+
error?: Error;
124134
}

src/useLDClientError.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useContext } from 'react';
2+
import context from './context';
3+
4+
/**
5+
* Provides the LaunchDarkly client initialization error, if there was one.
6+
*
7+
* @return The `launchdarkly-js-client-sdk` `LDClient` initialization error
8+
*/
9+
export default function useLDClientError() {
10+
const { error } = useContext(context);
11+
12+
return error;
13+
}

0 commit comments

Comments
 (0)