Reputation: 299
I'm starting a small project that uses TypeScript, Next.js, and Socket.io. I don't understand how to tell TypeScript that I am kind of "merging" these two. For example, here are my files.
/api/socket.ts
:
import { NextApiRequest, NextApiResponse } from 'next'
import { Server } from 'socket.io'
const SocketHandler = (req: NextApiRequest, res: NextApiResponse) => {
if (res.socket.server.io) {
console.log('Socket is already running.')
} else {
console.log('Socket is initializing...')
const io = new Server(res.socket.server)
res.socket.server.io = io
io.on('connection', (socket) => {
socket.on('input-change', (msg) => {
socket.broadcast.emit('update-input', msg)
})
})
}
res.end()
}
export default SocketHandler
/components/client.tsx
:
import { useEffect, useState } from 'react'
import io from 'socket.io-client'
import type { ChangeEvent } from 'react'
import type { Socket } from 'socket.io-client'
let socket: undefined | Socket
export default function Client() {
const [input, setInput] = useState('')
useEffect(() => {
socketInitializer()
}, [])
const socketInitializer = async () => {
fetch('/api/socket')
socket = io()
socket.on('connect', () => {
console.log('connected')
})
socket.on('update-input', (msg) => {
setInput(msg)
})
}
const onChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value)
if (socket !== undefined) {
socket.emit('input-change', e.target.value)
}
}
return (
<input
placeholder="Type something"
value={input}
onChange={onChangeHandler}
/>
)
}
And this code is working. But I'm getting all kinds of warnings / errors, like "Property 'server' does not exist on type 'Socket'"
, "Object is possibly 'null'."
(on res
itself).
I understand the main issue is TypeScript does not know I am adding .io
on the res.socket.server
object. But what I don't understand is A) how to tell it that I am adding that .io
, B) why res
and socket
are possibly null
, and C) why .server
does not exist on res.socket
, according to TypeScript.
I just need some direction and possibly higher-level explanation of how to tackle this. I think I need a .d.ts
file, or maybe I just a new interface, but I am not really sure how to properly write a new interface without over-riding types that are already in place.
Upvotes: 8
Views: 6476
Reputation: 147
First would it be better to do socket " io('/api/socket') to initialize the connection.
The fetch is aSync and it could be that your change is going before the connection is really up.
It also makes no sense to do the socket.on('input-change') in the on('connect') they all need to be defined before the connection is made.
Otherwise they gets recreated each time a connection is made.
Maybe these tips helps you a bit.
Upvotes: 0
Reputation: 91
If you're looking for a shorter alternative, you can use a type assertion with the as any
syntax:
const socket = req.socket as any;
Upvotes: -1
Reputation: 299
I found an answer. I don't know if it is the correct way of going about it, but this is what I did.
First I made new TypeScript interfaces, starting with the .io
property, and moved up all the way back to res
. All of these extend what NextApiResponse
already is, and just add new properties.
import type { Server as HTTPServer } from 'http'
import type { NextApiRequest, NextApiResponse } from 'next'
import type { Socket as NetSocket } from 'net'
import type { Server as IOServer } from 'socket.io'
interface SocketServer extends HTTPServer {
io?: IOServer | undefined
}
interface SocketWithIO extends NetSocket {
server: SocketServer
}
interface NextApiResponseWithSocket extends NextApiResponse {
socket: SocketWithIO
}
Then I just assigned my new NextApiResponseWithSocket
to the res
parameter.
const SocketHandler = (_: NextApiRequest, res: NextApiResponseWithSocket) => { ... }
If there is a better way to do this, I would love to know.
Upvotes: 20