socket.io-client
socket.io-client copied to clipboard
socket.on cannot accept generic "extends string" parameters
Describe the bug
When using a generic-extended argument as the parameter to socket.on, I cannot properly type the callback. The full error message is:
Argument of type '(data: any) => void' is not assignable to parameter of type 'FallbackToUntypedListener<E extends "connect" | "connect_error" | "disconnect" ? SocketReservedEvents[E] : E extends string ? (...args: any[]) => void : never>'.ts(2345)
To Reproduce
Please fill the following code example:
Server example omitted as this is a typecheck-only bug
Client
import { io } from "socket.io-client";
const socket = io("google.com");
// fine
socket.on("event", (data: any) => {});
// fine
type Events = "abc" | "def";
socket.on("abc" as Events, (data: any) => {});
// fine
function hookup(event: Events) {
socket.on(event, (data: any) => {})
}
// not fine
function hookup2<E extends string>(event: E) {
socket.on(event, (data: any) => {});
}
Expected behavior
Because parameterized type E extends string, the callback should be typed normally as a user-defined callback.
Instead, it seems to be defined as never or something? Not sure about that.
Workaround:
function hookup2<E extends string>(event: E) {
socket.on(event as string, (data: any) => {});
}
Platform:
- Device: HP Z Book
- OS: Windows 10
Additional context VS Code Version 1.75.0
"dependencies": {
"socket.io-client": "^4.5.4",
"typescript": "^4.9.5"
}
This appears to have been introduced in [email protected]. [email protected] does not exhibit this behavior, presumably because it does not have special-case handler types for "connect", "disconnect", etc.
Note that this also happens with a specifically typed Socket<> object:
import { io, Socket } from "socket.io-client";
interface MyEvents {
event: (data: any) => void;
abc: (data: any) => void;
def: (data: any) => void;
}
const socket: Socket<MyEvents, MyEvents> = io("google.com");
// fine
socket.on("event", (data: any) => {});
// fine
type Events = "abc" | "def";
socket.on("abc" as Events, (data: any) => {});
// fine
function hookup(event: Events) {
socket.on(event, (data: any) => {});
}
// not fine
function hookup2<E extends Events>(event: E) {
socket.on(event, (data: any) => {});
}
Hi! Support for typed events was added in version 4.0.0: https://github.com/socketio/socket.io-client/commit/59023657a02cf78f90522e0d7797749707ed5ed2
In your first example, TypeScript indeed chokes on E extends string:
// not fine
function hookup2<E extends string>(event: E) {
socket.on(event, (data: any) => {});
}
// fine
function hookup3(event: string) {
socket.on(event, (data: any) => {});
}
In your 2nd example:
function hookup2<E extends Events>(event: E) {
socket.on(event, (data: any) => {});
}
I think TypeScript complains because event may be neither "abc" nor "def", so it can't infer the callback signature from MyEvents.
Reference: https://socket.io/docs/v4/typescript/
Closing, as I don't think there is much we can do here. Please reopen if needed.