dmotz / trystero

✨🤝✨ Build instant multiplayer webapps, no server required — Magic WebRTC matchmaking over BitTorrent, Nostr, MQTT, IPFS, Supabase, and Firebase
https://oxism.com/trystero
MIT License
1.33k stars 92 forks source link

TypeScript Question: How to use room.makeAction when room is possible null? #59

Closed tyzh-dev closed 12 months ago

tyzh-dev commented 1 year ago

Hi there I am using the custom useRoom hook from the readme example to create a room. I modified it slightly to work with Next.JS SSR.

'use client';

import { type BaseRoomConfig, joinRoom } from 'trystero';
import { useCallback, useEffect, useRef } from 'react';
import { useAppDispatch } from '../redux/store';
import { updatePokerRoomId } from '../redux/feature/poker-room/pokerRoomSlice';

export const useMultiplayerRoom = (
  roomConfig: BaseRoomConfig,
  roomId: string
) => {
  const createRoom = useCallback(
    (roomConfig: BaseRoomConfig, roomId: string) => {
      if (typeof window !== 'undefined') {
        return joinRoom(roomConfig, roomId);
      }
    }, []
  );

  const roomRef = useRef(createRoom(roomConfig, roomId));

  useEffect(() => {
    if (typeof window !== 'undefined') {
      roomRef.current = createRoom(roomConfig, roomId);
      return () => {
        if (roomRef.current) {
          roomRef.current.leave();
        }
      };
    }
  }, [createRoom, roomConfig, roomId, setupMultiplayerRoomConnection]);

  return roomRef.current;
};

TypeScript gives me an error saying the room may be possibly null when I try to make an action using the room connection

 const [sendUser, getUser] = multiplayerPokerRoomConnection?.makeAction('user');

Error: Type '[ActionSender<unknown>, ActionReceiver<unknown>, ActionProgress] | undefined' is not an array type.

Any tips for solving this error? Does makeAction always need to be called as a hook or can I just check for the existence of a room in a function and register the event listener once in a function?

dmotz commented 1 year ago

The problem is that conditional that checks for window makes TypeScript think it could be undefined and not able to be destructured. Is that client check necessary if you're using 'use client'?

In any case, makeAction() isn't really a hook, just a normal function, so you can call it conditionally wherever you'd like.

tyzh-dev commented 12 months ago

Next.JS 13/14 renders everything completely on the server by default.

use client does opt out of React Server Components but you can still get Server Side Rendering and client-side hydration with Next.JS.

So if I use the following code:

"use client"
import {joinRoom} from 'trystero'
import {useEffect, useRef} from 'react'

export const useRoom = (roomConfig, roomId) => {
  const roomRef = useRef(joinRoom(roomConfig, roomId))

  useEffect(() => {
    roomRef.current = joinRoom(roomConfig, roomId)
    return () => roomRef.current.leave()
  }, [roomConfig, roomId])

  return roomRef.current
}

I'll get the following error from trystero:

 node_modules/simple-peer-light/index.js (98:0) @ new Peer
 ⨯ unhandledRejection: Error: No WebRTC support: Specify `opts.wrtc` option in this environment
    at new Peer (webpack-internal:///(ssr)/./node_modules/simple-peer-light/index.js:102:11)
    at initPeer (webpack-internal:///(ssr)/./node_modules/trystero/src/utils.js:26:16)
    at eval (webpack-internal:///(ssr)/./node_modules/trystero/src/torrent.js:64:75)
    at Array.map (<anonymous>)
    at makeOffers (webpack-internal:///(ssr)/./node_modules/trystero/src/torrent.js:63:10)
    at makeOfferPool (webpack-internal:///(ssr)/./node_modules/trystero/src/torrent.js:73:31)
    at announceAll (webpack-internal:///(ssr)/./node_modules/trystero/src/torrent.js:235:17) {
  code: 'ERR_WEBRTC_SUPPORT'

So directly calling joinRoom in the useRef doesn't work with Next.JS. I could make a new issue if you consider this a bug.

Anyway, being able to call makeAction conditionally solves this problem. Thanks for the tip!