Skip to content

Commit 6f13da7

Browse files
add test
1 parent d832ac0 commit 6f13da7

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

tests/unit/useOnyxTest.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,4 +1271,73 @@ describe('useOnyx', () => {
12711271
expect(result.current[1].status).toEqual('loaded');
12721272
});
12731273
});
1274+
1275+
// Regression coverage for Expensify/App#87850 ("[Onyx] Fix extra mount render introduced in useOnyx v3.0.59").
1276+
// The bug: `subscribe` unconditionally reset `resultRef.current` to a fresh tuple, including on initial mount.
1277+
// `useSyncExternalStore` then observed a different snapshot reference post-subscribe and scheduled an extra
1278+
// render per `useOnyx` hook. The fix guards the reset behind `hasMountedRef` so it only runs on re-subscription.
1279+
describe('initial mount render count', () => {
1280+
it('should render only once when the key has a value already in Onyx cache', async () => {
1281+
await Onyx.set(ONYXKEYS.TEST_KEY, 'cached_value');
1282+
1283+
let renderCount = 0;
1284+
const {result} = renderHook(() => {
1285+
renderCount++;
1286+
return useOnyx(ONYXKEYS.TEST_KEY);
1287+
});
1288+
1289+
await act(async () => waitForPromisesToResolve());
1290+
1291+
expect(result.current[0]).toEqual('cached_value');
1292+
expect(result.current[1].status).toEqual('loaded');
1293+
// A single render — no extra render caused by subscribe resetting state on initial mount.
1294+
expect(renderCount).toBe(1);
1295+
});
1296+
1297+
it('should render exactly twice (loading → loaded) when the key is not cached', async () => {
1298+
let renderCount = 0;
1299+
const {result} = renderHook(() => {
1300+
renderCount++;
1301+
return useOnyx(ONYXKEYS.TEST_KEY);
1302+
});
1303+
1304+
await act(async () => waitForPromisesToResolve());
1305+
1306+
expect(result.current[0]).toBeUndefined();
1307+
expect(result.current[1].status).toEqual('loaded');
1308+
// Exactly two renders: initial 'loading' + transition to 'loaded' after the connection callback fires.
1309+
// If the regression returns, a third render sneaks in from the subscribe-time state reset.
1310+
expect(renderCount).toBe(2);
1311+
});
1312+
1313+
it('should render exactly twice when the key value is only present in storage', async () => {
1314+
await StorageMock.setItem(ONYXKEYS.TEST_KEY, 'storage_value');
1315+
1316+
let renderCount = 0;
1317+
const {result} = renderHook(() => {
1318+
renderCount++;
1319+
return useOnyx(ONYXKEYS.TEST_KEY);
1320+
});
1321+
1322+
await act(async () => waitForPromisesToResolve());
1323+
1324+
expect(result.current[0]).toEqual('storage_value');
1325+
expect(result.current[1].status).toEqual('loaded');
1326+
expect(renderCount).toBe(2);
1327+
});
1328+
1329+
it('should render exactly twice for a non-cached collection member key', async () => {
1330+
let renderCount = 0;
1331+
const {result} = renderHook(() => {
1332+
renderCount++;
1333+
return useOnyx(`${ONYXKEYS.COLLECTION.TEST_KEY}1`);
1334+
});
1335+
1336+
await act(async () => waitForPromisesToResolve());
1337+
1338+
expect(result.current[0]).toBeUndefined();
1339+
expect(result.current[1].status).toEqual('loaded');
1340+
expect(renderCount).toBe(2);
1341+
});
1342+
});
12741343
});

0 commit comments

Comments
 (0)