@posthog/react 1.9.1 → 1.10.1
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 +7 -0
- package/dist/esm/index.js +11 -17
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/slim/index.js +11 -17
- package/dist/esm/slim/index.js.map +1 -1
- package/dist/types/index.d.ts +5 -7
- package/dist/types/slim/index.d.ts +5 -7
- package/dist/umd/index.js +11 -17
- package/dist/umd/index.js.map +1 -1
- package/dist/umd/slim/index.js +11 -17
- package/dist/umd/slim/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/PostHogFeature.tsx +15 -25
- package/src/hooks/__tests__/featureFlags.test.tsx +61 -0
- package/src/hooks/useFeatureFlagEnabled.ts +22 -3
|
@@ -115,6 +115,67 @@ describe('feature flag hooks', () => {
|
|
|
115
115
|
expect(result.current).toEqual(expected)
|
|
116
116
|
})
|
|
117
117
|
|
|
118
|
+
describe('useFeatureFlagEnabled defaultValue', () => {
|
|
119
|
+
// A client whose flags have loaded but where isFeatureEnabled returns undefined,
|
|
120
|
+
// i.e. the flag is absent from the payload. The default beforeEach mock coerces
|
|
121
|
+
// every result with `!!`, so it can never produce the undefined we need here.
|
|
122
|
+
function renderWithUnknownFlag() {
|
|
123
|
+
const client = {
|
|
124
|
+
isFeatureEnabled: () => undefined,
|
|
125
|
+
onFeatureFlags: () => () => {},
|
|
126
|
+
featureFlags: {
|
|
127
|
+
hasLoadedFlags: true,
|
|
128
|
+
} as unknown as PostHog['featureFlags'],
|
|
129
|
+
} as unknown as PostHog
|
|
130
|
+
|
|
131
|
+
// eslint-disable-next-line react/display-name
|
|
132
|
+
return ({ children }: { children: React.ReactNode }) => (
|
|
133
|
+
<PostHogProvider client={client}>{children}</PostHogProvider>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
it.each([
|
|
138
|
+
[false as boolean | undefined, false],
|
|
139
|
+
[true as boolean | undefined, true],
|
|
140
|
+
[undefined, undefined],
|
|
141
|
+
])('returns %s for an unknown flag when defaultValue is %s', (defaultValue, expected) => {
|
|
142
|
+
const { result } = renderHook(() => useFeatureFlagEnabled('missing', defaultValue as boolean), {
|
|
143
|
+
wrapper: renderWithUnknownFlag(),
|
|
144
|
+
})
|
|
145
|
+
expect(result.current).toBe(expected)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('prefers the real flag value over the default', () => {
|
|
149
|
+
const { result } = renderHook(() => useFeatureFlagEnabled('example_feature_false', true), {
|
|
150
|
+
wrapper: renderProvider,
|
|
151
|
+
})
|
|
152
|
+
expect(result.current).toBe(false)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('applies the default in the bootstrap branch when the flag is absent', () => {
|
|
156
|
+
const client = {
|
|
157
|
+
isFeatureEnabled: () => undefined,
|
|
158
|
+
onFeatureFlags: () => () => {},
|
|
159
|
+
config: {
|
|
160
|
+
bootstrap: {
|
|
161
|
+
featureFlags: { other_flag: true },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
featureFlags: {
|
|
165
|
+
hasLoadedFlags: false,
|
|
166
|
+
} as unknown as PostHog['featureFlags'],
|
|
167
|
+
} as unknown as PostHog
|
|
168
|
+
|
|
169
|
+
// eslint-disable-next-line react/display-name
|
|
170
|
+
const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
171
|
+
<PostHogProvider client={client}>{children}</PostHogProvider>
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const { result } = renderHook(() => useFeatureFlagEnabled('my_flag', false), { wrapper })
|
|
175
|
+
expect(result.current).toBe(false)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
118
179
|
describe('useFeatureFlagResult', () => {
|
|
119
180
|
describe('bootstrap fallback', () => {
|
|
120
181
|
function renderWithBootstrap(
|
|
@@ -2,7 +2,25 @@ import { useContext, useEffect, useState } from 'react'
|
|
|
2
2
|
import { PostHogContext } from '../context'
|
|
3
3
|
import { isUndefined } from '../utils/type-utils'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Check whether a feature flag is enabled for the current user.
|
|
7
|
+
*
|
|
8
|
+
* Returns `undefined` while flags are still loading or when the flag is absent, so callers can
|
|
9
|
+
* distinguish "not known yet" from "disabled".
|
|
10
|
+
*
|
|
11
|
+
* @param flag Key of the feature flag.
|
|
12
|
+
* @returns Whether the flag is enabled, or `undefined` if not yet loaded or not found.
|
|
13
|
+
*/
|
|
14
|
+
export function useFeatureFlagEnabled(flag: string): boolean | undefined
|
|
15
|
+
/**
|
|
16
|
+
* Check whether a feature flag is enabled for the current user.
|
|
17
|
+
*
|
|
18
|
+
* @param flag Key of the feature flag.
|
|
19
|
+
* @param defaultValue Returned instead of `undefined` while flags are loading or when the flag is absent.
|
|
20
|
+
* @returns Whether the flag is enabled, falling back to `defaultValue` when the value is unknown.
|
|
21
|
+
*/
|
|
22
|
+
export function useFeatureFlagEnabled(flag: string, defaultValue: boolean): boolean
|
|
23
|
+
export function useFeatureFlagEnabled(flag: string, defaultValue?: boolean): boolean | undefined {
|
|
6
24
|
const { client, bootstrap } = useContext(PostHogContext)
|
|
7
25
|
|
|
8
26
|
const [featureEnabled, setFeatureEnabled] = useState<boolean | undefined>(() => client.isFeatureEnabled(flag))
|
|
@@ -17,8 +35,9 @@ export function useFeatureFlagEnabled(flag: string): boolean | undefined {
|
|
|
17
35
|
|
|
18
36
|
// if the client is not loaded yet, check if we have a bootstrapped value and then true/false it
|
|
19
37
|
if (!client?.featureFlags?.hasLoadedFlags && bootstrap?.featureFlags) {
|
|
20
|
-
return isUndefined(bootstrapped) ?
|
|
38
|
+
return isUndefined(bootstrapped) ? defaultValue : !!bootstrapped
|
|
21
39
|
}
|
|
22
40
|
|
|
23
|
-
|
|
41
|
+
// while the flag value is unknown (flags not loaded, or the flag is absent), fall back to defaultValue
|
|
42
|
+
return isUndefined(featureEnabled) ? defaultValue : featureEnabled
|
|
24
43
|
}
|