UNPKG

react-use-action-cable-ts

Version:

Hooks to easily use Rails Action Cable in your React application

155 lines (154 loc) 5.87 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.useActionCable = useActionCable; exports.useChannel = useChannel; const actioncable_1 = require("@rails/actioncable"); const camelcase_keys_1 = __importDefault(require("camelcase-keys")); const react_1 = require("react"); const snakecase_keys_1 = __importDefault(require("snakecase-keys")); function useActionCable(url, { verbose } = {}) { const actionCable = (0, react_1.useMemo)(() => (0, actioncable_1.createConsumer)(url), [url]); (0, react_1.useEffect)(() => { if (verbose) console.info("useActionCable: Created Action Cable"); return () => { if (verbose) console.info("useActionCable: Disconnected Action Cable"); actionCable.disconnect(); }; }, [actionCable, verbose]); return { actionCable }; } function useChannel(actionCable, { verbose, receiveCamelCase, sendSnakeCase } = { verbose: false, receiveCamelCase: true, sendSnakeCase: true }) { const [queue, setQueue] = (0, react_1.useState)([]); const [connected, setConnected] = (0, react_1.useState)(false); const [subscribed, setSubscribed] = (0, react_1.useState)(false); const channelRef = (0, react_1.useRef)(null); const subscribe = (0, react_1.useCallback)((data, callbacks) => { if (verbose) console.info(`useChannel: Connecting to ${data.channel}`); const channel = actionCable.subscriptions.create(sendSnakeCase ? (0, snakecase_keys_1.default)(data, { deep: true }) : data, { rejected: () => { if (verbose) console.info(`useChannel: Rejected`); callbacks.rejected?.(); }, received: (x) => { if (verbose) console.info(`useChannel: Received ${JSON.stringify(x)}`); if (receiveCamelCase && x) { x = (0, camelcase_keys_1.default)(x, { deep: true }); } callbacks.received?.(x); }, initialized: () => { if (verbose) console.info(`useChannel: Init ${data.channel}`); setSubscribed(true); callbacks.initialized?.(); }, connected: () => { if (verbose) console.info(`useChannel: Connected to ${data.channel}`); setConnected(true); callbacks.connected?.(); }, disconnected: () => { if (verbose) console.info(`useChannel: Disconnected`); setConnected(false); callbacks.disconnected?.(); } }); channelRef.current = channel; }, [actionCable, receiveCamelCase, sendSnakeCase, verbose]); const unsubscribe = (0, react_1.useCallback)(() => { setSubscribed(false); if (channelRef.current) { if (verbose) console.info(`useChannel: Unsubscribing from ${channelRef.current.identifier}`); // @ts-expect-error actionCable.subscriptions.remove(channelRef.current); channelRef.current = null; } }, [actionCable, verbose]); const perform = (0, react_1.useCallback)((action, payload) => { if (subscribed && !connected) throw Error("useChannel: not connected"); if (!subscribed) throw Error("useChannel: not subscribed"); try { if (verbose) console.info(`useChannel: Sending ${action} with payload ${JSON.stringify(payload)}`); channelRef.current?.perform(action, payload); } catch { throw Error("useChannel: Unknown error"); } }, [connected, subscribed, verbose]); const processQueue = (0, react_1.useCallback)(() => { const action = queue[0]; try { perform(action.action, action.payload); setQueue((prevState) => { const q = [...prevState]; q.shift(); return q; }); } catch { if (verbose) console.warn(`useChannel: Unable to perform action '${action.action}'. It will stay at the front of the queue.`); } }, [perform, queue, verbose]); (0, react_1.useEffect)(() => { return () => { unsubscribe(); }; }, [unsubscribe]); (0, react_1.useEffect)(() => { if (subscribed && connected && queue.length > 0) { processQueue(); } else if ((!subscribed || !connected) && queue.length > 0) { if (verbose) console.info(`useChannel: Queue paused. Subscribed: ${subscribed}. Connected: ${connected}. Queue length: ${queue.length}`); } }, [queue, connected, subscribed, processQueue, verbose]); const enqueue = (action, payload) => { if (verbose) console.info(`useChannel: Adding action to queue - ${action}: ${JSON.stringify(payload)}`); setQueue((prevState) => [ ...prevState, { action, payload } ]); }; const send = ({ action, payload, useQueue }) => { const formattedPayload = sendSnakeCase && payload ? (0, snakecase_keys_1.default)(payload, { deep: true }) : payload; if (useQueue) { enqueue(action, formattedPayload); } else { perform(action, formattedPayload); } }; return { subscribe, unsubscribe, send }; }