Skip to content

Commit b783ef4

Browse files
authored
fix: Throw a better error message if context is null (#192)
Now throws an error message if context is not set.
1 parent cf9d52c commit b783ef4

11 files changed

+53
-33
lines changed

src/FlagContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ export interface IFlagContextValue
1717
>;
1818
}
1919

20-
const FlagContext = React.createContext<IFlagContextValue>(null as never);
20+
const FlagContext = React.createContext<IFlagContextValue | null>(null);
2121

2222
export default FlagContext;

src/FlagProvider.test.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import React, { useContext, useEffect, useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { render, screen } from '@testing-library/react';
33
import { type Mock } from 'vitest';
44
import { UnleashClient, type IVariant, EVENTS } from 'unleash-proxy-client';
55
import FlagProvider from './FlagProvider';
6-
import FlagContext from './FlagContext';
6+
import { useFlagContext } from './useFlagContext';
77
import '@testing-library/jest-dom';
88

99
const getVariantMock = vi.fn().mockReturnValue('A');
@@ -44,8 +44,7 @@ vi.mock('unleash-proxy-client', async (importOriginal) => {
4444
const noop = () => {};
4545

4646
const FlagConsumerAfterClientInit = () => {
47-
const { updateContext, isEnabled, getVariant, client, on } =
48-
useContext(FlagContext);
47+
const { updateContext, isEnabled, getVariant, client, on } = useFlagContext();
4948
const [enabled, setIsEnabled] = useState(false);
5049
const [variant, setVariant] = useState<IVariant | null>(null);
5150
const [context, setContext] = useState<any>('nothing');
@@ -71,8 +70,7 @@ const FlagConsumerAfterClientInit = () => {
7170
};
7271

7372
const FlagConsumerBeforeClientInit = () => {
74-
const { updateContext, isEnabled, getVariant, client, on } =
75-
useContext(FlagContext);
73+
const { updateContext, isEnabled, getVariant, client, on } = useFlagContext();
7674
const [enabled, setIsEnabled] = useState(false);
7775
const [variant, setVariant] = useState<IVariant | null>(null);
7876
const [context, setContext] = useState<any>('nothing');
@@ -162,8 +160,7 @@ test('A memoized consumer should not rerender when the context provider values a
162160
const renderCounter = vi.fn();
163161

164162
const MemoizedConsumer = React.memo(() => {
165-
const { updateContext, isEnabled, getVariant, client, on } =
166-
useContext(FlagContext);
163+
const { updateContext, isEnabled, getVariant, client, on } = useFlagContext();
167164

168165
renderCounter();
169166

src/integration.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import React, { useContext } from 'react';
1+
import React from 'react';
22
import { render, screen, waitFor } from '@testing-library/react';
33
import { EVENTS, UnleashClient } from 'unleash-proxy-client';
44
import FlagProvider from './FlagProvider';
55
import useFlagsStatus from './useFlagsStatus';
66
import { act } from 'react-dom/test-utils';
77
import useFlag from './useFlag';
8+
import { useFlagContext } from './useFlagContext';
89
import useVariant from './useVariant';
9-
import FlagContext from './FlagContext';
1010

1111
const fetchMock = vi.fn(async () => {
1212
return Promise.resolve({
@@ -89,7 +89,7 @@ test('should render toggles', async () => {
8989

9090
test('should be ready from the start if bootstrapped', () => {
9191
const Component = React.memo(() => {
92-
const { flagsReady } = useContext(FlagContext);
92+
const { flagsReady } = useFlagContext();
9393

9494
return <>{flagsReady ? 'ready' : ''}</>;
9595
});
@@ -183,7 +183,7 @@ test('should render limited times when bootstrapped', async () => {
183183

184184
const Component = () => {
185185
const enabled = useFlag('test-flag');
186-
const { flagsReady } = useContext(FlagContext);
186+
const { flagsReady } = useFlagContext();
187187

188188
renders += 1;
189189

@@ -229,7 +229,7 @@ test('should resolve values before setting flagsReady', async () => {
229229

230230
const Component = () => {
231231
const enabled = useFlag('test-flag');
232-
const { flagsReady } = useContext(FlagContext);
232+
const { flagsReady } = useFlagContext();
233233

234234
renders += 1;
235235

src/useFlag.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useContext, useEffect, useState, useRef } from 'react';
2-
import FlagContext from './FlagContext';
1+
import { useEffect, useState, useRef } from 'react';
2+
import { useFlagContext } from './useFlagContext';
33

44
const useFlag = (featureName: string) => {
5-
const { isEnabled, client } = useContext(FlagContext);
5+
const { isEnabled, client } = useFlagContext();
66
const [flag, setFlag] = useState(!!isEnabled(featureName));
77
const flagRef = useRef<typeof flag>();
88
flagRef.current = flag;

src/useFlagContext.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { renderHook } from '@testing-library/react-hooks/native';
2+
import FlagProvider from "./FlagProvider";
3+
import { useFlagContext } from "./useFlagContext";
4+
5+
test("throws an error if used outside of a FlagProvider", () => {
6+
const { result } = renderHook(() => useFlagContext());
7+
8+
expect(result.error).toEqual(
9+
Error("This hook must be used within a FlagProvider")
10+
);
11+
});
12+
13+
test("does not throw an error if used inside of a FlagProvider", () => {
14+
const { result } = renderHook(() => useFlagContext(), { wrapper: FlagProvider });
15+
16+
expect(result.error).toBeUndefined();
17+
});

src/useFlagContext.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useContext } from 'react';
2+
import FlagContext from './FlagContext';
3+
4+
export function useFlagContext() {
5+
const context = useContext(FlagContext);
6+
if (!context) {
7+
throw new Error('This hook must be used within a FlagProvider');
8+
}
9+
return context;
10+
}

src/useFlags.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useContext, useEffect, useState } from 'react';
2-
import FlagContext from './FlagContext';
1+
import { useEffect, useState } from 'react';
2+
import { useFlagContext } from './useFlagContext';
33

44
const useFlags = () => {
5-
const { client } = useContext(FlagContext);
5+
const { client } = useFlagContext();
66
const [flags, setFlags] = useState(client.getAllToggles());
77

88
useEffect(() => {

src/useFlagsStatus.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
/** @format */
2-
3-
import { useContext } from 'react';
4-
import FlagContext from './FlagContext';
2+
import { useFlagContext } from './useFlagContext';
53

64
const useFlagsStatus = () => {
7-
const { flagsReady, flagsError } = useContext(FlagContext);
5+
const { flagsReady, flagsError } = useFlagContext();
86

97
return { flagsReady, flagsError };
108
};

src/useUnleashClient.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { useContext } from 'react';
2-
import FlagContext from './FlagContext';
1+
import { useFlagContext } from './useFlagContext';
32

43
const useUnleashClient = () => {
5-
const { client } = useContext(FlagContext);
4+
const { client } = useFlagContext();
65
return client;
76
};
87

src/useUnleashContext.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { useContext } from 'react';
2-
import FlagContext from './FlagContext';
1+
import { useFlagContext } from './useFlagContext';
32

43
const useUnleashContext = () => {
5-
const { updateContext } = useContext(FlagContext);
4+
const { updateContext } = useFlagContext();
65

76
return updateContext;
87
};

src/useVariant.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { useContext, useState, useEffect, useRef } from 'react';
1+
import { useState, useEffect, useRef } from 'react';
22
import { IVariant } from 'unleash-proxy-client';
3-
import FlagContext from './FlagContext';
3+
import { useFlagContext } from './useFlagContext';
44

55
export const variantHasChanged = (
66
oldVariant: IVariant,
@@ -17,7 +17,7 @@ export const variantHasChanged = (
1717
};
1818

1919
const useVariant = (featureName: string): Partial<IVariant> => {
20-
const { getVariant, client } = useContext(FlagContext);
20+
const { getVariant, client } = useFlagContext();
2121

2222
const [variant, setVariant] = useState(getVariant(featureName));
2323
const variantRef = useRef<typeof variant>({

0 commit comments

Comments
 (0)