react-use-action-cable-ts
Version:
Hooks to easily use Rails Action Cable in your React application
155 lines (154 loc) • 5.87 kB
JavaScript
;
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
};
}