Created
March 4, 2023 15:04
-
-
Save thestrabusiness/38404b3269082f5b00f7639e37a1e613 to your computer and use it in GitHub Desktop.
React + ActionCable Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class GameChannel < ApplicationCable::Channel | |
def subscribed | |
game = Game.find(params[:id]) | |
stream_for game | |
end | |
end | |
## When the game state changes | |
GameChannel.broadcast_to(game, GameSerializer.render(game)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import useActionCable from './hooks/useActionCable'; | |
import useChannel from './hooks/useChannel'; | |
const {actionCable} = useActionCable(`${baseURI.webSocket}/cable`); | |
const {subscribe, unsubscribe} = useChannel<string>(actionCable); | |
useEffect(() => { | |
subscribe( | |
{channel: 'GameChannel', id: gameId}, | |
{ | |
received: gameJson => { | |
const parsedGame = JSON.parse(gameJson); | |
setGame(parsedGame); | |
}, | |
}, | |
); | |
return () => { | |
unsubscribe(); | |
}; | |
}, [subscribe, unsubscribe, gameId]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useEffect, useMemo} from 'react'; | |
import {createConsumer} from '@rails/actioncable'; | |
const useActionCable = (url: string) => { | |
const actionCable = useMemo(() => createConsumer(url), [url]); | |
useEffect(() => { | |
return () => { | |
console.log('Disconnect Action Cable'); | |
actionCable.disconnect(); | |
}; | |
}, [actionCable]); | |
return {actionCable}; | |
}; | |
export default useActionCable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Consumer, Subscription} from '@rails/actioncable'; | |
import {useState, useEffect, useRef, useCallback} from 'react'; | |
// Needed for @rails/actioncable | |
let global: any; | |
global.addEventListener = () => {}; | |
global.removeEventListener = () => {}; | |
type Data = { | |
channel: string; | |
id?: number; | |
}; | |
type Callbacks<T> = { | |
received?: (_message: T) => void; | |
initialized?: () => void; | |
connected?: () => void; | |
disconnected?: () => void; | |
}; | |
export default function useChannel<ReceivedType>(actionCable: Consumer) { | |
const [connected, setConnected] = useState(false); | |
const [subscribed, setSubscribed] = useState(false); | |
const channelRef = useRef<Subscription<Consumer> | null>(null); | |
const subscribe = (data: Data, callbacks: Callbacks<ReceivedType>) => { | |
console.log(`useChannel - INFO: Connecting to ${data.channel}`); | |
const channel = actionCable.subscriptions.create(data, { | |
received: (message: ReceivedType) => { | |
if (callbacks.received) { | |
callbacks.received(message); | |
} | |
}, | |
initialized: () => { | |
console.log('useChannel - INFO: Init ' + data.channel); | |
setSubscribed(true); | |
if (callbacks.initialized) { | |
callbacks.initialized(); | |
} | |
}, | |
connected: () => { | |
console.log('useChannel - INFO: Connected to ' + data.channel); | |
setConnected(true); | |
if (callbacks.connected) { | |
callbacks.connected(); | |
} | |
}, | |
disconnected: () => { | |
console.log('useChannel - INFO: Disconnected'); | |
setConnected(false); | |
if (callbacks.disconnected) { | |
callbacks.disconnected(); | |
} | |
}, | |
}); | |
channelRef.current = channel; | |
}; | |
const unsubscribe = useCallback(() => { | |
setSubscribed(false); | |
if (channelRef.current) { | |
console.log( | |
'useChannel - INFO: Unsubscribing from ' + | |
channelRef.current.identifier, | |
); | |
channelRef.current.unsubscribe(); | |
channelRef.current = null; | |
} | |
}, []); | |
useEffect(() => { | |
return () => { | |
unsubscribe(); | |
}; | |
}, [unsubscribe]); | |
const send = (action: string, payload: {} | undefined) => { | |
if (subscribed && !connected) { | |
throw 'useChannel - ERROR: not connected'; | |
} | |
if (!subscribed) { | |
throw 'useChannel - ERROR: not subscribed'; | |
} | |
try { | |
channelRef?.current?.perform(action, payload); | |
} catch (e) { | |
throw 'useChannel - ERROR: ' + e; | |
} | |
}; | |
return {subscribe, unsubscribe, send}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment