Skip to content

Commit

Permalink
BugFix: isCacheLoaded was always false on remounts (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-statsig authored Jun 27, 2023
1 parent f41b2f6 commit 071c041
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 15 deletions.
37 changes: 22 additions & 15 deletions src/StatsigProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
}
});

Expand All @@ -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();
});

Expand Down Expand Up @@ -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(),
Expand All @@ -227,7 +229,7 @@ export default function StatsigProvider({
}),
}),
[
isInitialized,
hasNetworkValues,
statsigPromise,
userVersion,
Statsig.initializeCalled(),
Expand All @@ -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;
}

Expand Down
93 changes: 93 additions & 0 deletions src/__tests__/CachingAndUpdatingUsers.test.tsx
Original file line number Diff line number Diff line change
@@ -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 <div>{userID}</div>;
}

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(
<StatsigProvider
sdkKey="client-dummy-key"
user={{ userID: 'a-user' }}
waitForCache={true}
>
<TestComponent />
</StatsigProvider>,
);

render(
<StatsigProvider
sdkKey="client-dummy-key"
user={{ userID: 'b-user' }}
waitForCache={true}
>
<TestComponent />
</StatsigProvider>,
);

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
]);
});
});

0 comments on commit 071c041

Please sign in to comment.