Files
explore/lib/socket/useSocketChannel.tsx
T
tom goriunovandGitHub 8f16d530c8 OP Superchain Explorer: MVP (#2786)
* context for storing subchain config and tx view for subchain

* address page and block page for subchain

* address aggregated page: local txs tab

* connect MultichainSelect to context

* home page placeholder

* refactor subchain select state

* envs for demo

* fix ts

* clear value for l2 review

* subchain widgets on home page

* csp policies

* sockets, duck and goose

* fix socket on subchain address page

* link builder for subchain views

* update home widget

* fix time increment

* enable tx interpretator and metadata service

* generate multichain config based on every chain app config

* Fix the multichain config to work in Docker

* multichain socket test

* rename subchain-id to subchain-slug path param

* refactoring

* update chain icons on entities

* home page: latest local txs

* latest cross-chain txs

* minor improvements

* renaming, pt. 1

* rename chain routes

* enable blockchain interaction

* add loading state to icon shield

* fix build

* fix tests

* update types package
2025-07-03 15:07:00 +02:00

86 lines
2.4 KiB
TypeScript

import type { Channel } from 'phoenix';
import { useEffect, useRef, useState } from 'react';
import { useSocket } from './context';
interface Params {
topic: string | undefined;
params?: object;
isDisabled: boolean;
onJoin?: (channel: Channel, message: unknown) => void;
onSocketClose?: () => void;
onSocketError?: () => void;
}
export default function useSocketChannel({ topic, params, isDisabled, onJoin, onSocketClose, onSocketError }: Params) {
const { socket, channelRegistry } = useSocket() || {};
const [ channel, setChannel ] = useState<Channel>();
const onCloseRef = useRef<string>(undefined);
const onErrorRef = useRef<string>(undefined);
const onJoinRef = useRef(onJoin);
onJoinRef.current = onJoin;
useEffect(() => {
const cleanUpRefs = () => {
const refs = [ onCloseRef.current, onErrorRef.current ].filter(Boolean);
refs.length > 0 && socket?.off(refs);
};
if (!isDisabled) {
onCloseRef.current = onSocketClose && socket?.onClose(onSocketClose);
onErrorRef.current = onSocketError && socket?.onError(onSocketError);
} else {
cleanUpRefs();
}
return cleanUpRefs;
}, [ onSocketClose, onSocketError, socket, isDisabled ]);
useEffect(() => {
if (isDisabled && channel) {
channel.leave();
setChannel(undefined);
}
}, [ channel, isDisabled ]);
useEffect(() => {
if (!socket || isDisabled || !topic || !channelRegistry) {
return;
}
let ch: Channel;
if (channelRegistry.current[topic]) {
ch = channelRegistry.current[topic].channel;
channelRegistry.current[topic].subscribers++;
onJoinRef.current?.(ch, '');
} else {
ch = socket.channel(topic);
channelRegistry.current[topic] = { channel: ch, subscribers: 1 };
ch.join()
.receive('ok', (message) => onJoinRef.current?.(ch, message))
.receive('error', () => {
onSocketError?.();
});
}
setChannel(ch);
const currentRegistry = channelRegistry.current;
return () => {
if (currentRegistry[topic]) {
currentRegistry[topic].subscribers > 0 && currentRegistry[topic].subscribers--;
if (currentRegistry[topic].subscribers === 0) {
ch.leave();
delete currentRegistry[topic];
}
}
setChannel(undefined);
};
}, [ socket, topic, params, isDisabled, onSocketError, channelRegistry ]);
return channel;
}