Skip to content

Commit 2512d84

Browse files
authored
Merge pull request #66 from launchdarkly/christie/sc-164279/unearth-ld-client-init-errors-in-react-sdk
Add error hook
2 parents 08d7a59 + 5ec6084 commit 2512d84

File tree

10 files changed

+74
-10
lines changed

10 files changed

+74
-10
lines changed

src/__snapshots__/provider.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ exports[`LDProvider render app 1`] = `
44
<Provider
55
value={
66
Object {
7+
"error": undefined,
78
"flagKeyMap": Object {},
89
"flags": Object {},
910
"ldClient": undefined,

src/__snapshots__/withLDProvider.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ exports[`withLDProvider render app 1`] = `
44
<Provider
55
value={
66
Object {
7+
"error": undefined,
78
"flagKeyMap": Object {},
89
"flags": Object {},
910
"ldClient": undefined,

src/asyncWithLDProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import getFlagsProxy from './getFlagsProxy';
3333
export default async function asyncWithLDProvider(config: AsyncProviderConfig) {
3434
const { clientSideID, user, flags: targetFlags, options, reactOptions: userReactOptions } = config;
3535
const reactOptions = { ...defaultReactOptions, ...userReactOptions };
36-
const { ldClient, flags: fetchedFlags } = await initLDClient(clientSideID, user, options, targetFlags);
36+
const { ldClient, flags: fetchedFlags, error } = await initLDClient(clientSideID, user, options, targetFlags);
3737

3838
const LDProvider = ({ children }: { children: ReactNode }) => {
3939
const [ldData, setLDData] = useState({
@@ -69,7 +69,7 @@ export default async function asyncWithLDProvider(config: AsyncProviderConfig) {
6969

7070
const { flags, flagKeyMap } = ldData;
7171

72-
return <Provider value={{ flags, flagKeyMap, ldClient }}>{children}</Provider>;
72+
return <Provider value={{ flags, flagKeyMap, ldClient, error }}>{children}</Provider>;
7373
};
7474

7575
return LDProvider;

src/context.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ interface LDContext {
2525
* @see https://docs.launchdarkly.com/sdk/client-side/javascript
2626
*/
2727
ldClient?: LDClient;
28+
29+
/**
30+
* LaunchDarkly client initialization error, if there was one.
31+
*/
32+
error?: Error;
2833
}
2934

3035
/**

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: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ describe('initLDClient', () => {
2929
beforeEach(() => {
3030
mockLDClient = {
3131
on: (e: string, cb: () => void) => {
32-
cb();
32+
if (e === 'ready') {
33+
cb();
34+
}
3335
},
36+
off: jest.fn(),
3437
allFlags: () => flags,
3538
variation: jest.fn(() => true),
3639
};
@@ -69,4 +72,17 @@ describe('initLDClient', () => {
6972
]);
7073
expect(mockLDClient.variation).toHaveBeenCalledTimes(0);
7174
});
75+
76+
test('returns an error', async () => {
77+
const error = new Error('Out of cheese');
78+
mockLDClient.on = (e: string, cb: (err: Error) => void) => {
79+
if (e === 'failed') {
80+
cb(error);
81+
}
82+
};
83+
84+
const flagsClient = await initLDClient(clientSideID);
85+
86+
expect(flagsClient).toEqual({ flags: {}, ldClient: mockLDClient, error });
87+
});
7288
});

src/initLDClient.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,21 @@ const initLDClient = async (
3030
const ldClient = ldClientInitialize(clientSideID, user, { ...wrapperOptions, ...options });
3131

3232
return new Promise<AllFlagsLDClient>((resolve) => {
33-
ldClient.on('ready', () => {
33+
function cleanup() {
34+
ldClient.off('ready', handleReady);
35+
ldClient.off('failed', handleFailure);
36+
}
37+
function handleFailure(error: Error) {
38+
cleanup();
39+
resolve({ flags: {}, ldClient, error });
40+
}
41+
function handleReady() {
42+
cleanup();
3443
const flags = fetchFlags(ldClient, targetFlags);
3544
resolve({ flags, ldClient });
36-
});
45+
}
46+
ldClient.on('failed', handleFailure);
47+
ldClient.on('ready', handleReady);
3748
});
3849
};
3950

src/provider.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,16 @@ class LDProvider extends Component<PropsWithChildren<ProviderConfig>, LDHocState
7878
let ldClient = await this.props.ldClient;
7979
const reactOptions = this.getReactOptions();
8080
let unproxiedFlags;
81+
let error: Error | undefined;
8182
if (ldClient) {
8283
unproxiedFlags = fetchFlags(ldClient, flags);
8384
} else {
8485
const initialisedOutput = await initLDClient(clientSideID, user, options, flags);
8586
unproxiedFlags = initialisedOutput.flags;
8687
ldClient = initialisedOutput.ldClient;
88+
error = initialisedOutput.error;
8789
}
88-
this.setState({ unproxiedFlags, ...getFlagsProxy(ldClient, unproxiedFlags, reactOptions, flags), ldClient });
90+
this.setState({ unproxiedFlags, ...getFlagsProxy(ldClient, unproxiedFlags, reactOptions, flags), ldClient, error });
8991
this.subscribeToChanges(ldClient);
9092
};
9193

@@ -107,9 +109,9 @@ class LDProvider extends Component<PropsWithChildren<ProviderConfig>, LDHocState
107109
}
108110

109111
render() {
110-
const { flags, flagKeyMap, ldClient } = this.state;
112+
const { flags, flagKeyMap, ldClient, error } = this.state;
111113

112-
return <Provider value={{ flags, flagKeyMap, ldClient }}>{this.props.children}</Provider>;
114+
return <Provider value={{ flags, flagKeyMap, ldClient, error }}>{this.props.children}</Provider>;
113115
}
114116
}
115117

src/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export interface LDReactOptions {
2424

2525
/**
2626
* Whether to send flag evaluation events when a flag is read from the `flags` object
27-
* retured by the `useFlags` hook. This is true by default, meaning flag evaluation
27+
* returned by the `useFlags` hook. This is true by default, meaning flag evaluation
2828
* events will be sent by default.
2929
*/
3030
sendEventsOnFlagRead?: boolean;
@@ -129,6 +129,11 @@ export interface AllFlagsLDClient {
129129
* @see https://docs.launchdarkly.com/sdk/client-side/javascript
130130
*/
131131
ldClient: LDClient;
132+
133+
/**
134+
* LaunchDarkly client initialization error, if there was one.
135+
*/
136+
error?: Error;
132137
}
133138

134139
/**

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)