Skip to content

Commit 81078fa

Browse files
fix(runtime): avoid HMRClient.disable startup race
React Native may throw "Expected HMRClient.setup()" if disable() runs before setup(). Retry disable() briefly on that specific invariant so harness startup stays stable. Add a unit test for the retry helper.
1 parent 1d7c811 commit 81078fa

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
import { disableHMRWhenReady } from '../disableHMRWhenReady.js';
4+
5+
describe('initialize', () => {
6+
it('retries HMRClient.disable until setup is ready', async () => {
7+
vi.useFakeTimers();
8+
9+
const disable = vi
10+
.fn()
11+
.mockImplementationOnce(() => {
12+
throw new Error('Expected HMRClient.setup() call at startup.');
13+
})
14+
.mockImplementationOnce(() => {
15+
// ok
16+
});
17+
18+
disableHMRWhenReady(disable, 50);
19+
await vi.runAllTimersAsync();
20+
21+
expect(disable).toHaveBeenCalledTimes(2);
22+
});
23+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export function disableHMRWhenReady(
2+
disable: () => void,
3+
retriesLeft: number,
4+
schedule: (cb: () => void) => void = (cb) => setTimeout(cb, 0),
5+
) {
6+
try {
7+
disable();
8+
} catch (error) {
9+
if (
10+
retriesLeft > 0 &&
11+
error instanceof Error &&
12+
error.message.includes('Expected HMRClient.setup() call at startup.')
13+
) {
14+
schedule(() => disableHMRWhenReady(disable, retriesLeft - 1, schedule));
15+
return;
16+
}
17+
18+
throw error;
19+
}
20+
}

packages/runtime/src/initialize.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getDeviceDescriptor } from './client/getDeviceDescriptor.js';
22
import { getClient } from './client/index.js';
3+
import { disableHMRWhenReady } from './disableHMRWhenReady.js';
34
import { setupJestMock } from './jest-mock.js';
45

56
// Polyfill for EventTarget
@@ -21,7 +22,7 @@ const HMRClient =
2122

2223
// Wait for HMRClient to be initialized
2324
setTimeout(() => {
24-
HMRClient.disable();
25+
disableHMRWhenReady(() => HMRClient.disable(), 50);
2526

2627
// Initialize the React Native Harness
2728
void getClient().then((client) =>

0 commit comments

Comments
 (0)