Skip to content

Instantly share code, notes, and snippets.

@thestrabusiness
Created March 4, 2023 15:04
Show Gist options
  • Save thestrabusiness/38404b3269082f5b00f7639e37a1e613 to your computer and use it in GitHub Desktop.
Save thestrabusiness/38404b3269082f5b00f7639e37a1e613 to your computer and use it in GitHub Desktop.
React + ActionCable Example
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))
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]);
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;
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