Skip to content

Commit 5380dc3

Browse files
authored
fix: catch init error in useFlagsmith (#351)
* Revert "fix: CORS errors due to header changes (#342)" This reverts commit 5c89532. * fix: surface-init-error-in-react-wrappers * fix: added-unhandled-error-test * fix: linter
1 parent b05a67c commit 5380dc3

File tree

5 files changed

+305
-196
lines changed

5 files changed

+305
-196
lines changed

.prettierrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"proseWrap": "always",
33
"singleQuote": true,
44
"printWidth": 120,
5-
"trailingComma": "all",
65
"tabWidth": 4,
6+
"semi": false,
77
"overrides": [
88
{
99
"files": "*.md",

flagsmith-core.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,10 @@ const Flagsmith = class {
829829
options.headers['Flagsmith-Application-Version'] = this.applicationMetadata.version;
830830
}
831831

832+
if (SDK_VERSION) {
833+
options.headers['Flagsmith-SDK-user-agent'] = `flagsmith-js-sdk/${SDK_VERSION}`
834+
}
835+
832836
if (headers) {
833837
Object.assign(options.headers, headers);
834838
}

react.tsx

Lines changed: 60 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,30 @@
1-
import React, {
2-
createContext,
3-
FC,
4-
useCallback,
5-
useContext,
6-
useEffect,
7-
useMemo,
8-
useRef,
9-
useState,
10-
} from 'react';
11-
import Emitter from './utils/emitter';
12-
const events = new Emitter();
1+
import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
2+
import Emitter from './utils/emitter'
3+
const events = new Emitter()
134

145
import { IFlagsmith, IFlagsmithTrait, IFlagsmithFeature, IState } from './types'
156

16-
export const FlagsmithContext = createContext<IFlagsmith<string,string> | null>(null)
7+
export const FlagsmithContext = createContext<IFlagsmith<string, string> | null>(null)
178
export type FlagsmithContextType = {
189
flagsmith: IFlagsmith // The flagsmith instance
1910
options?: Parameters<IFlagsmith['init']>[0] // Initialisation options, if you do not provide this you will have to call init manually
2011
serverState?: IState
21-
children: React.ReactNode;
12+
children: React.ReactNode
2213
}
2314

24-
export const FlagsmithProvider: FC<FlagsmithContextType> = ({
25-
flagsmith, options, serverState, children,
26-
}) => {
15+
export const FlagsmithProvider: FC<FlagsmithContextType> = ({ flagsmith, options, serverState, children }) => {
2716
const firstRenderRef = useRef(true)
2817
if (flagsmith && !flagsmith?._trigger) {
2918
flagsmith._trigger = () => {
3019
// @ts-expect-error using internal function, consumers would never call this
31-
flagsmith.log("React - trigger event received")
32-
events.emit('event');
20+
flagsmith?.log('React - trigger event received')
21+
events.emit('event')
3322
}
3423
}
3524

3625
if (flagsmith && !flagsmith?._triggerLoadingState) {
3726
flagsmith._triggerLoadingState = () => {
38-
events.emit('loading_event');
27+
events.emit('loading_event')
3928
}
4029
}
4130

@@ -46,22 +35,24 @@ export const FlagsmithProvider: FC<FlagsmithContextType> = ({
4635
if (firstRenderRef.current) {
4736
firstRenderRef.current = false
4837
if (options) {
49-
flagsmith.init({
50-
...options,
51-
state: options.state || serverState,
52-
onChange: (...args) => {
53-
if (options.onChange) {
54-
options.onChange(...args)
55-
}
56-
},
57-
})
38+
flagsmith
39+
.init({
40+
...options,
41+
state: options.state || serverState,
42+
onChange: (...args) => {
43+
if (options.onChange) {
44+
options.onChange(...args)
45+
}
46+
},
47+
})
48+
.catch((error) => {
49+
// @ts-expect-error using internal function, consumers would never call this
50+
flagsmith?.log('React - Failed to initialize flagsmith', error)
51+
events.emit('event')
52+
})
5853
}
5954
}
60-
return (
61-
<FlagsmithContext.Provider value={flagsmith}>
62-
{children}
63-
</FlagsmithContext.Provider>
64-
)
55+
return <FlagsmithContext.Provider value={flagsmith}>{children}</FlagsmithContext.Provider>
6556
}
6657

6758
const useConstant = function <T>(value: T): T {
@@ -72,7 +63,6 @@ const useConstant = function <T>(value: T): T {
7263
return ref.current
7364
}
7465

75-
7666
const flagsAsArray = (_flags: any): string[] => {
7767
if (typeof _flags === 'string') {
7868
return [_flags]
@@ -82,29 +72,26 @@ const flagsAsArray = (_flags: any): string[] => {
8272
return _flags
8373
}
8474
}
85-
throw new Error(
86-
'Flagsmith: please supply an array of strings or a single string of flag keys to useFlags',
87-
)
75+
throw new Error('Flagsmith: please supply an array of strings or a single string of flag keys to useFlags')
8876
}
8977

9078
const getRenderKey = (flagsmith: IFlagsmith, flags: string[], traits: string[] = []) => {
9179
return flags
9280
.map((k) => {
9381
return `${flagsmith.getValue(k)}${flagsmith.hasFeature(k)}`
94-
}).concat(traits.map((t) => (
95-
`${flagsmith.getTrait(t)}`
96-
)))
82+
})
83+
.concat(traits.map((t) => `${flagsmith.getTrait(t)}`))
9784
.join(',')
9885
}
9986

10087
export function useFlagsmithLoading() {
101-
const flagsmith = useContext(FlagsmithContext);
102-
const [loadingState, setLoadingState] = useState(flagsmith?.loadingState);
103-
const [subscribed, setSubscribed] = useState(false);
88+
const flagsmith = useContext(FlagsmithContext)
89+
const [loadingState, setLoadingState] = useState(flagsmith?.loadingState)
90+
const [subscribed, setSubscribed] = useState(false)
10491
const refSubscribed = useRef(subscribed)
10592

10693
const eventListener = useCallback(() => {
107-
setLoadingState(flagsmith?.loadingState);
94+
setLoadingState(flagsmith?.loadingState)
10895
}, [flagsmith])
10996
if (!refSubscribed.current) {
11097
events.on('loading_event', eventListener)
@@ -120,26 +107,23 @@ export function useFlagsmithLoading() {
120107
if (subscribed) {
121108
events.off('loading_event', eventListener)
122109
}
123-
};
110+
}
124111
}, [flagsmith, subscribed, eventListener])
125112

126113
return loadingState
127114
}
128115

129-
type UseFlagsReturn<
130-
F extends string | Record<string, any>,
131-
T extends string
132-
> = [F] extends [string]
116+
type UseFlagsReturn<F extends string | Record<string, any>, T extends string> = F extends string
133117
? {
134-
[K in F]: IFlagsmithFeature;
135-
} & {
136-
[K in T]: IFlagsmithTrait;
137-
}
118+
[K in F]: IFlagsmithFeature
119+
} & {
120+
[K in T]: IFlagsmithTrait
121+
}
138122
: {
139-
[K in keyof F]: IFlagsmithFeature<F[K]>;
140-
} & {
141-
[K in T]: IFlagsmithTrait;
142-
};
123+
[K in keyof F]: IFlagsmithFeature<F[K]>
124+
} & {
125+
[K in T]: IFlagsmithTrait
126+
}
143127

144128
/**
145129
* Example usage:
@@ -154,61 +138,58 @@ type UseFlagsReturn<
154138
* }
155139
* useFlags<MyFeatureInterface>(["featureOne", "featureTwo"]);
156140
*/
157-
export function useFlags<
158-
F extends string | Record<string, any>,
159-
T extends string = string
160-
>(
161-
_flags: readonly (F | keyof F)[], _traits: readonly T[] = []
162-
){
141+
export function useFlags<F extends string | Record<string, any>, T extends string = string>(
142+
_flags: readonly (F | keyof F)[],
143+
_traits: readonly T[] = []
144+
) {
163145
const firstRender = useRef(true)
164146
const flags = useConstant<string[]>(flagsAsArray(_flags))
165147
const traits = useConstant<string[]>(flagsAsArray(_traits))
166148
const flagsmith = useContext(FlagsmithContext)
167-
const [renderRef, setRenderRef] = useState(getRenderKey(flagsmith as IFlagsmith, flags, traits));
149+
const [renderRef, setRenderRef] = useState(getRenderKey(flagsmith as IFlagsmith, flags, traits))
168150
const eventListener = useCallback(() => {
169151
const newRenderKey = getRenderKey(flagsmith as IFlagsmith, flags, traits)
170152
if (newRenderKey !== renderRef) {
171153
// @ts-expect-error using internal function, consumers would never call this
172-
flagsmith?.log("React - useFlags flags and traits have changed")
154+
flagsmith?.log('React - useFlags flags and traits have changed')
173155
setRenderRef(newRenderKey)
174156
}
175157
}, [renderRef])
176-
const emitterRef = useRef(events.once('event', eventListener));
177-
178-
158+
const emitterRef = useRef(events.once('event', eventListener))
179159

180160
if (firstRender.current) {
181-
firstRender.current = false;
161+
firstRender.current = false
182162
// @ts-expect-error using internal function, consumers would never call this
183-
flagsmith?.log("React - Initialising event listeners")
163+
flagsmith?.log('React - Initialising event listeners')
184164
}
185165

186-
useEffect(()=>{
166+
useEffect(() => {
187167
return () => {
188168
emitterRef.current?.()
189169
}
190170
}, [])
191171

192172
const res = useMemo(() => {
193173
const res: any = {}
194-
flags.map((k) => {
174+
flags
175+
.map((k) => {
195176
res[k] = {
196177
enabled: flagsmith!.hasFeature(k),
197178
value: flagsmith!.getValue(k),
198179
}
199-
}).concat(traits?.map((v) => {
180+
})
181+
.concat(
182+
traits?.map((v) => {
200183
res[v] = flagsmith!.getTrait(v)
201-
}))
184+
})
185+
)
202186
return res
203187
}, [renderRef])
204188

205189
return res as UseFlagsReturn<F, T>
206190
}
207191

208-
export function useFlagsmith<
209-
F extends string | Record<string, any>,
210-
T extends string = string
211-
>() {
192+
export function useFlagsmith<F extends string | Record<string, any>, T extends string = string>() {
212193
const context = useContext(FlagsmithContext)
213194

214195
if (!context) {

test/init.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ describe('Flagsmith.init', () => {
292292
headers: expect.objectContaining({
293293
'Flagsmith-Application-Name': 'Test App',
294294
'Flagsmith-Application-Version': '1.2.3',
295+
'Flagsmith-SDK-user-agent': `flagsmith-js-sdk/${SDK_VERSION}`,
295296
}),
296297
}),
297298
);

0 commit comments

Comments
 (0)