From 38321d076a8afbfc57cee6a1fe513b64f7b1d410 Mon Sep 17 00:00:00 2001 From: Jeremy Zongker Date: Mon, 15 Dec 2025 18:10:29 -0600 Subject: [PATCH] Fixing hydration and race issues --- src/components/LoadingPage.tsx | 2 +- src/components/layouts/Footer.tsx | 4 ++-- src/components/video/LiveStream.tsx | 16 +++++++++++++--- src/components/video/StreamingHeader.tsx | 2 ++ src/components/video/VideoContainer.tsx | 11 ++++++++--- src/helpers/ChatHelper.ts | 19 +++++++++++++------ 6 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/components/LoadingPage.tsx b/src/components/LoadingPage.tsx index d797e88c..1a860a58 100644 --- a/src/components/LoadingPage.tsx +++ b/src/components/LoadingPage.tsx @@ -9,7 +9,7 @@ export const LoadingPage: React.FC = (props) => { const imgSrc = AppearanceHelper.getLogoLight(props.config?.appearance, "/images/logo.png") return (
- logo + {imgSrc && logo}
) diff --git a/src/components/layouts/Footer.tsx b/src/components/layouts/Footer.tsx index 67e6e961..1d5e8992 100644 --- a/src/components/layouts/Footer.tsx +++ b/src/components/layouts/Footer.tsx @@ -18,8 +18,8 @@ export function Footer(props: Props) { return } else { - let logoUrl = AppearanceHelper.getLogoDark(props.config?.appearance, "/images/logo.png"); - const photo = {props.config?.church.name} + const logoUrl = AppearanceHelper.getLogoDark(props.config?.appearance, "/images/logo.png"); + const photo = logoUrl ? {props.config?.church.name} : null; return ( <> diff --git a/src/components/video/LiveStream.tsx b/src/components/video/LiveStream.tsx index 2fa4ff58..49a56593 100644 --- a/src/components/video/LiveStream.tsx +++ b/src/components/video/LiveStream.tsx @@ -27,18 +27,28 @@ export const LiveStream: React.FC = (props) => { const [currentService, setCurrentService] = React.useState(null); const [overlayContent, setOverlayContent] = React.useState(false); const [isClient, setIsClient] = React.useState(false); + const [chatReady, setChatReady] = React.useState(false); + const joinedServiceIdRef = React.useRef(null); const loadData = async (keyName: string) => { let result: StreamConfigInterface = await fetch(`${EnvironmentHelper.Common.ContentApi}/preview/data/${keyName}`).then((response: Response) => response.json()); StreamingServiceHelper.updateServiceTimes(result); result.keyName = keyName; ChatConfigHelper.current = result; - if (props.includeInteraction) await ChatHelper.initChat(); + if (props.includeInteraction) { + await ChatHelper.initChat(); + setChatReady(true); + } setConfig(result); } const checkJoinRooms = () => { - if (props.includeInteraction && currentService && config) { + // Only join rooms after chat is fully initialized + if (props.includeInteraction && currentService && config && chatReady) { + // Prevent duplicate joins for the same service + if (joinedServiceIdRef.current === currentService.id) return; + joinedServiceIdRef.current = currentService.id; + StreamChatManager.joinMainRoom(ChatConfigHelper.current.churchId, currentService, setChatState); StreamChatManager.checkHost(config, currentService.id, chatState, setChatState); } @@ -59,7 +69,7 @@ export const LiveStream: React.FC = (props) => { loadData(props.keyName); }, []); - React.useEffect(checkJoinRooms, [currentService]); //eslint-disable-line + React.useEffect(checkJoinRooms, [currentService, chatReady, config]); //eslint-disable-line let result = (
{(props.includeHeader) && } diff --git a/src/components/video/StreamingHeader.tsx b/src/components/video/StreamingHeader.tsx index 92104a46..67a47500 100644 --- a/src/components/video/StreamingHeader.tsx +++ b/src/components/video/StreamingHeader.tsx @@ -74,6 +74,8 @@ export const StreamingHeader: React.FC = (props) => { let imgSrc = (props.appearance) ? AppearanceHelper.getLogo(props.appearance, "images/logo-header.png", "/images/logo.png", "#FFF") : ""; + // Guard against the string "null" being stored in the database + if (imgSrc === "null") imgSrc = ""; React.useEffect(() => { if (!props.appearance) { diff --git a/src/components/video/VideoContainer.tsx b/src/components/video/VideoContainer.tsx index 272055e4..7e5f93b4 100644 --- a/src/components/video/VideoContainer.tsx +++ b/src/components/video/VideoContainer.tsx @@ -87,12 +87,17 @@ export const VideoContainer: React.FC = (props) => { } const getLogo = () => { + let logo: string | null = null; if (transparent) { const textColor = StyleHelper.getTextColor(props.sections[0]?.textColor, config?.globalStyles, config?.appearance); - const logo = AppearanceHelper.getLogoByTextColor(config?.appearance?.logoLight || null, config?.appearance?.logoDark || null, textColor); - return logo !== "" ? logo : null; + logo = AppearanceHelper.getLogoByTextColor(config?.appearance?.logoLight || null, config?.appearance?.logoDark || null, textColor); } - else return config?.appearance?.logoDark || null; + else { + logo = config?.appearance?.logoDark || null; + } + // Return null for any falsy value or the string "null" + if (!logo || logo === "null") return null; + return logo; } const contentType = React.useMemo(() => { diff --git a/src/helpers/ChatHelper.ts b/src/helpers/ChatHelper.ts index 46dcf2b6..9c69bc61 100644 --- a/src/helpers/ChatHelper.ts +++ b/src/helpers/ChatHelper.ts @@ -22,19 +22,24 @@ export class ChatHelper { blockedIps: [] }) - private static initialized = false; + private static initializing = false; static initChat = async () => { - // Prevent double initialization - if (ChatHelper.initialized) return; - ChatHelper.initialized = true; + // If socket is already connected and has a socketId, no need to reinitialize + if (SocketHelper.isConnected?.() && SocketHelper.socketId) { + return; + } + + // Prevent concurrent initialization attempts + if (ChatHelper.initializing) return; + ChatHelper.initializing = true; // Init socket first - catch errors so they don't break the page try { await SocketHelper.init(); } catch { - // Socket init failed - reset initialized flag so it can be retried - ChatHelper.initialized = false; + // Socket init failed - allow retry + ChatHelper.initializing = false; return; } @@ -49,6 +54,8 @@ export class ChatHelper { SocketHelper.addHandler("videoChatInvite", "chatVideoChatInvite", ChatHelper.handleVideoChatInvite); SocketHelper.addHandler("reconnect", "chatReconnect", ChatHelper.handleReconnect); SocketHelper.addHandler("blockedIp", "chatBlockedIp", ChatHelper.handleBlockedIps); + + ChatHelper.initializing = false; } static handleReconnect = () => {