Skip to content

Commit 52bc820

Browse files
authored
fix useWebSocket error (#2176) (#2177)
* fix: useWebSocket don't call connect when socketUrl is empty (#2176) * fix: useWebSocket unlimited reconnect when update socketUrl (#2176) * fix: disconnect not works * docs: modify useWebSocket docs
1 parent 7121d08 commit 52bc820

File tree

4 files changed

+99
-22
lines changed

4 files changed

+99
-22
lines changed

packages/hooks/src/useWebSocket/__tests__/index.test.ts

+77-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const promise: Promise<void> = new Promise((resolve) => resolve());
77
const wsUrl = 'ws://localhost:9999';
88

99
describe('useWebSocket', () => {
10-
afterAll(() => {
10+
afterEach(() => {
1111
WS.clean();
1212
});
1313

@@ -44,6 +44,21 @@ describe('useWebSocket', () => {
4444
expect(hooks.result.current.readyState).toBe(ReadyState.Closed);
4545
});
4646

47+
it('disconnect should work', async () => {
48+
const wsServer = new WS(wsUrl);
49+
const hooks = renderHook(() => useWebSocket(wsUrl));
50+
51+
// connect
52+
expect(hooks.result.current.readyState).toBe(ReadyState.Connecting);
53+
await act(() => wsServer.connected);
54+
expect(hooks.result.current.readyState).toBe(ReadyState.Open);
55+
56+
// disconnect
57+
act(() => hooks.result.current.disconnect());
58+
await act(() => wsServer.closed);
59+
expect(hooks.result.current.readyState).toBe(ReadyState.Closed);
60+
});
61+
4762
it('useWebSocket should be manually triggered', async () => {
4863
const wsServer = new WS(wsUrl);
4964

@@ -68,4 +83,65 @@ describe('useWebSocket', () => {
6883

6984
act(() => wsServer.close());
7085
});
86+
87+
it('should not call connect when initial socketUrl is empty', async () => {
88+
const wsServer = new WS(wsUrl);
89+
const onOpen = jest.fn();
90+
const onClose = jest.fn();
91+
92+
let url = '';
93+
const hooks = renderHook(() => useWebSocket(url, { onOpen, onClose }));
94+
95+
await act(async () => {
96+
await sleep(1000);
97+
});
98+
99+
expect(hooks.result.current.readyState).toBe(ReadyState.Closed);
100+
101+
url = wsUrl;
102+
hooks.rerender();
103+
104+
await act(async () => {
105+
await wsServer.connected;
106+
});
107+
108+
expect(hooks.result.current.readyState).toBe(ReadyState.Open);
109+
expect(onOpen).toBeCalledTimes(1);
110+
111+
act(() => wsServer.close());
112+
});
113+
114+
it('change socketUrl should connect correctly', async () => {
115+
const wsUrl1 = 'ws://localhost:8888';
116+
const wsServer1 = new WS(wsUrl);
117+
const wsServer2 = new WS(wsUrl1);
118+
119+
const onOpen = jest.fn();
120+
const onClose = jest.fn();
121+
122+
let url = wsUrl;
123+
const hooks = renderHook(() => useWebSocket(url, { onOpen, onClose, reconnectInterval: 300 }));
124+
125+
expect(hooks.result.current.readyState).toBe(ReadyState.Connecting);
126+
await act(async () => {
127+
await wsServer1.connected;
128+
});
129+
expect(hooks.result.current.readyState).toBe(ReadyState.Open);
130+
131+
url = wsUrl1;
132+
hooks.rerender();
133+
await act(async () => {
134+
await wsServer2.connected;
135+
});
136+
expect(hooks.result.current.readyState).toBe(ReadyState.Open);
137+
138+
await act(async () => {
139+
await sleep(3000);
140+
});
141+
expect(onOpen).toBeCalledTimes(2);
142+
expect(onClose).toBeCalledTimes(1);
143+
144+
act(() => wsServer1.close());
145+
act(() => wsServer2.close());
146+
});
71147
});

packages/hooks/src/useWebSocket/index.en-US.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ interface Options {
3535

3636
interface Result {
3737
latestMessage?: WebSocketEventMap['message'];
38-
sendMessage?: WebSocket['send'];
39-
disconnect?: () => void;
40-
connect?: () => void;
38+
sendMessage: WebSocket['send'];
39+
disconnect: () => void;
40+
connect: () => void;
4141
readyState: ReadyState;
4242
webSocketIns?: WebSocket;
4343
}

packages/hooks/src/useWebSocket/index.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ export interface Options {
2424

2525
export interface Result {
2626
latestMessage?: WebSocketEventMap['message'];
27-
sendMessage?: WebSocket['send'];
28-
disconnect?: () => void;
29-
connect?: () => void;
27+
sendMessage: WebSocket['send'];
28+
disconnect: () => void;
29+
connect: () => void;
3030
readyState: ReadyState;
3131
webSocketIns?: WebSocket;
3232
}
@@ -52,8 +52,6 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
5252
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout>>();
5353
const websocketRef = useRef<WebSocket>();
5454

55-
const unmountedRef = useRef(false);
56-
5755
const [latestMessage, setLatestMessage] = useState<WebSocketEventMap['message']>();
5856
const [readyState, setReadyState] = useState<ReadyState>(ReadyState.Closed);
5957

@@ -87,35 +85,38 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
8785
setReadyState(ReadyState.Connecting);
8886

8987
ws.onerror = (event) => {
90-
if (unmountedRef.current) {
88+
if (websocketRef.current !== ws) {
9189
return;
9290
}
9391
reconnect();
9492
onErrorRef.current?.(event, ws);
9593
setReadyState(ws.readyState || ReadyState.Closed);
9694
};
9795
ws.onopen = (event) => {
98-
if (unmountedRef.current) {
96+
if (websocketRef.current !== ws) {
9997
return;
10098
}
10199
onOpenRef.current?.(event, ws);
102100
reconnectTimesRef.current = 0;
103101
setReadyState(ws.readyState || ReadyState.Open);
104102
};
105103
ws.onmessage = (message: WebSocketEventMap['message']) => {
106-
if (unmountedRef.current) {
104+
if (websocketRef.current !== ws) {
107105
return;
108106
}
109107
onMessageRef.current?.(message, ws);
110108
setLatestMessage(message);
111109
};
112110
ws.onclose = (event) => {
113-
if (unmountedRef.current) {
114-
return;
115-
}
116-
reconnect();
117111
onCloseRef.current?.(event, ws);
118-
setReadyState(ws.readyState || ReadyState.Closed);
112+
// closed by server
113+
if (websocketRef.current === ws) {
114+
reconnect();
115+
}
116+
// closed by disconnect or closed by server
117+
if (!websocketRef.current || websocketRef.current === ws) {
118+
setReadyState(ws.readyState || ReadyState.Closed);
119+
}
119120
};
120121

121122
websocketRef.current = ws;
@@ -141,16 +142,16 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
141142

142143
reconnectTimesRef.current = reconnectLimit;
143144
websocketRef.current?.close();
145+
websocketRef.current = undefined;
144146
};
145147

146148
useEffect(() => {
147-
if (!manual) {
149+
if (!manual && socketUrl) {
148150
connect();
149151
}
150152
}, [socketUrl, manual]);
151153

152154
useUnmount(() => {
153-
unmountedRef.current = true;
154155
disconnect();
155156
});
156157

packages/hooks/src/useWebSocket/index.zh-CN.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ interface Options {
3535

3636
interface Result {
3737
latestMessage?: WebSocketEventMap['message'];
38-
sendMessage?: WebSocket['send'];
39-
disconnect?: () => void;
40-
connect?: () => void;
38+
sendMessage: WebSocket['send'];
39+
disconnect: () => void;
40+
connect: () => void;
4141
readyState: ReadyState;
4242
webSocketIns?: WebSocket;
4343
}

0 commit comments

Comments
 (0)