diff --git a/src/StatsigProvider.tsx b/src/StatsigProvider.tsx index f25a3e7..3e61cc3 100644 --- a/src/StatsigProvider.tsx +++ b/src/StatsigProvider.tsx @@ -118,8 +118,8 @@ export default function StatsigProvider({ _reactNativeDependencies: rnDeps, }: Props): JSX.Element { const isReactNative = !!rnDeps; - const [isCacheLoaded, setCacheLoaded] = useState(false); - const [isInitialized, setInitialized] = useState(false); + const [hasCacheValues, setHasCacheValues] = useState(false); + const [hasNetworkValues, setHasNetworkValues] = useState(false); const resolver = useRef<(() => void) | null>(null); const [userVersion, setUserVersion] = useState(0); @@ -142,14 +142,16 @@ export default function StatsigProvider({ }); const unmount = mountKey === undefined || prevMountKey !== mountKey; if (unmount) { - setInitialized(false); + setHasNetworkValues(false); + setHasCacheValues(false); } Statsig.updateUser(user).then(() => { resolver.current && resolver.current(); setUserVersion((version) => version + 1); if (unmount) { - setInitialized(true); + setHasNetworkValues(true); + setHasCacheValues(true); } }); @@ -176,11 +178,11 @@ export default function StatsigProvider({ } Statsig.setOnCacheLoadedCallback(() => { - setCacheLoaded(true); + setHasCacheValues(true); }); Statsig.initialize(sdkKey, userMemo, options).then(() => { - setInitialized(true); + setHasNetworkValues(true); resolver.current && resolver.current(); }); @@ -208,15 +210,15 @@ export default function StatsigProvider({ const child = pickChildToRender( waitForCache === true, waitForInitialization === true, - isInitialized, - isCacheLoaded, + hasNetworkValues, + hasCacheValues, children, initializingComponent, ); const contextValue = useMemo( () => ({ - initialized: isInitialized, + initialized: hasNetworkValues, statsigPromise, userVersion, initStarted: Statsig.initializeCalled(), @@ -227,7 +229,7 @@ export default function StatsigProvider({ }), }), [ - isInitialized, + hasNetworkValues, statsigPromise, userVersion, Statsig.initializeCalled(), @@ -244,23 +246,28 @@ export default function StatsigProvider({ function pickChildToRender( waitForCache: boolean, waitForInitialization: boolean, - isInitialized: boolean, - isCacheLoaded: boolean, + hasNetworkValues: boolean, + hasCacheValues: boolean, children: React.ReactNode | React.ReactNode[], initializingComponent?: React.ReactNode | React.ReactNode[], ): React.ReactNode | React.ReactNode[] | null { - // Don't wait + // No Need to Wait + if (hasNetworkValues) { + return children; + } + + // Has to wait, but don't want to if (waitForInitialization !== true && waitForCache !== true) { return children; } // Wait until cache is ready - if (waitForCache && isCacheLoaded) { + if (waitForCache && hasCacheValues) { return children; } // Wait until initialized from network - if (waitForInitialization && isInitialized) { + if (waitForInitialization && hasNetworkValues) { return children; } diff --git a/src/__tests__/CachingAndUpdatingUsers.test.tsx b/src/__tests__/CachingAndUpdatingUsers.test.tsx new file mode 100644 index 0000000..e3adcd4 --- /dev/null +++ b/src/__tests__/CachingAndUpdatingUsers.test.tsx @@ -0,0 +1,93 @@ +/** + * @jest-environment jsdom + */ + +import '@testing-library/jest-dom'; +import { render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; +import StatsigJS from 'statsig-js'; +import { Statsig, StatsigProvider, useConfig } from '..'; +import * as TestInitializeData from './single_gate_init_response.json'; + +StatsigJS.encodeIntializeCall = false; + +let renderedUserIDs: string[] = []; + +function TestComponent() { + const { config } = useConfig('a_config'); + + const userID = config.get('result', 'not-found'); + renderedUserIDs.push(userID); + + return
{userID}
; +} + +function createResponseFromExtractedUserID(userID: unknown) { + return { + ...TestInitializeData, + ...{ + dynamic_configs: { + a_config: { + name: 'a_config', + value: { + result: userID, + }, + rule_id: 'a_rule_id', + group: 'a_group', + is_device_based: false, + secondary_exposures: [], + }, + }, + }, + }; +} + +describe('Caching and Updating Users', () => { + (global as any).fetch = jest.fn((_url, request) => { + const response = createResponseFromExtractedUserID( + JSON.parse(request.body).user.userID, + ); + + return Promise.resolve({ + ok: true, + status: 200, + text: () => JSON.stringify(response), + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + (Statsig as any).instance = null; + renderedUserIDs = []; + }); + + it('waits for cache and updates users', async () => { + render( + + + , + ); + + render( + + + , + ); + + await waitFor(() => screen.getByText('b-user')); + + expect(renderedUserIDs).toEqual([ + 'not-found', // cache loaded for a-user, no result + 'not-found', // cache loaded for b-user, no result + 'b-user', // network loaded for b-user + ]); + }); +});