graysonlee123
graysonlee123

Reputation: 299

Working with TypeScript, Next.js, and Socket.io

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

Answers (3)

Niels Thiebosch
Niels Thiebosch

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

gabrielsalvador
gabrielsalvador

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

graysonlee123
graysonlee123

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

Related Questions