I’m encountering a rejoin issue using the @zoom/meetingsdk
(v3.13.2) with a React application (react@18
).
When embedding a Zoom webinar:
- The first join works fine.
- After leaving the webinar and trying to rejoin:
- On the host side, the Zoom UI loads but appears at the top of the page, not in the intended container (
meetingSDKElement
). - On the participant side, the rejoin attempt results in a completely blank screen.
- On the host side, the Zoom UI loads but appears at the top of the page, not in the intended container (
I’ve ensured proper cleanup (leaveMeeting
, unmounting components, etc.) before reinitializing, but the issue persists.
I’m attaching screenshots for both host and participant views showing the issue during rejoin.
Steps to Reproduce:
- Join webinar (host and participant) via the SDK.
- Leave the webinar using
client.leaveMeeting()
. - Attempt to rejoin the same webinar.
- Observe:
- Host view: Zoom UI appears at the top of the page.
- Participant view: Blank screen.
Expected Behavior:
Zoom UI should reinitialize in the correct DOM container and display properly for both host and participant on rejoin.
Attachments:
- Host UI after rejoin (screenshot)
- Participant blank screen (screenshot)
code:
import React, { useEffect, useRef, useState } from ‘react’;
import { useSelector } from ‘react-redux’;
import { accountSelectors } from ‘@/redux/selectors/account.selectors’;
import { LIVE_VIDEO_ASPECT_RATIO, LIVE_TOTAL_CONTROL_BAR_HEIGHT } from ‘@/constants/video’;
import ZoomMtgEmbedded from ‘@zoom/meetingsdk/embedded’;
import AccountService from ‘@/services/account.service’;
import styles from ‘./ZoomWebinarEmbed.module.sass’;
const ZoomWebinar = ({ meetingDetails, isHost = false, isBigPopup = false, onEndWebinar }) => {
const { account, user } = useSelector(accountSelectors);
const meetingRef = useRef(null);
const client = useRef(ZoomMtgEmbedded.createClient());
const sdkKey = process.env.NEXT_PUBLIC_ZENLER_ZOOM_SDKKEY;
const meetingNumber = meetingDetails.meetingId;
const password = meetingDetails.password;
const zak = isHost ? meetingDetails.zak : null;
const role = isHost ? 1 : 0;
const userName = user.name;
const userEmail = isHost ? meetingDetails.email : “sample@gmail.com”;
const observerRef = useRef(null);
const wrapperRef = useRef(null);
const shareObserverRef = useRef(null);
const [hasInitialized, setHasInitialized] = useState(false);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [isReady, setIsReady] = useState(false);
const [sharing, setSharing] = useState(false);
// Update dimensions when wrapper size changes
useEffect(() => {
if (!wrapperRef.current) return;
const updateDimensions = () => {
const { clientWidth, clientHeight } = wrapperRef.current;
const isMobile = window.innerWidth <= 768;
let width, height;
if (isMobile) {
width = clientWidth;
height = clientWidth / LIVE_VIDEO_ASPECT_RATIO;
} else {
const maxWidth = isBigPopup ? clientWidth * 0.99 : clientWidth;
width = Math.min(maxWidth, 1280);
height = width / LIVE_VIDEO_ASPECT_RATIO;
}
const maxAllowedHeight = clientHeight - LIVE_TOTAL_CONTROL_BAR_HEIGHT;
if (height > maxAllowedHeight && isBigPopup) {
height = Math.floor(maxAllowedHeight * 0.99);
width = height * LIVE_VIDEO_ASPECT_RATIO;
}
console.log("1111111111111111111111111111111");
console.log("width ",width);
console.log("height ",height);
setDimensions({
width: Math.floor(width),
height: Math.floor(height)
});
setIsReady(true);
};
updateDimensions();
const resizeObserver = new ResizeObserver(updateDimensions);
resizeObserver.observe(wrapperRef.current);
return () => resizeObserver.disconnect();
}, );
//initial style
useEffect(() => {
const style = document.createElement(‘style’);
style.innerHTML = `
div[aria-label=“Zoom Web SDK Widget”][role=“region”] {
border-radius: 0 !important;
width: 100% !important;
height: 100% !important;
padding: 0 !important;
}
.zoom-MuiPaper-root {
border: none !important;
box-shadow: none !important;
}
.zoom-MuiPopper-root,
.zoom-MuiBackdrop-root,
.zoom-MuiTooltip-popper {
z-index: 9999 !important;
}
i.zmicon:not([aria-label="Shield Checkmark"]) {
font-size: 25px !important;
}
/* Hide the button with specific aria-label */
button[aria-label="See apps that are accessing your meeting content"] {
display: none !important;
}
/* Hide the Zoom view mode tabs */
.zoom-MuiTabs-root {
display: none !important;
}
/* Add 10px gap between toolbar buttons */
.css-woj72k {
display: flex !important;
gap: 10px !important;
}
/* Ensure Zoom video stays visible */
.css-cmusf3 {
visibility: visible !important;
}
`;
document.head.appendChild(style);
console.log(“22222222222222222222222222”);
return () => {
document.head.removeChild(style);
disconnectObserver();
disconnectShareObserver();
if (client.current) {
console.log("Leaving Zoom meeting...");
client.current.leaveMeeting().catch(err => {
console.log('Error leaving meeting:', err);
});
}
};
}, );
// Initialize meeting only when dimensions are ready
useEffect(() => {
if (isReady && dimensions.width > 0 && dimensions.height > 0) {
console.log(“33333333333333333333”);
getSignature();
}
}, [isReady, dimensions]);
useEffect(() => {
if (!hasInitialized) return;
const enforceZoomSize = () => {
console.log('44444444444444444444444444444444');
//for client side end screen share display issue
const videoContainer = document.querySelector('.css-1d28g70');
if (videoContainer && !sharing && !isHost) {
videoContainer.style.width = `${dimensions.width}px`;
videoContainer.style.height = `${dimensions.height}px`;
videoContainer.removeAttribute('width');
videoContainer.removeAttribute('height');
}
//applying dimension width and height to both host and client side
const containers = wrapperRef.current?.querySelectorAll(
'div[aria-label="Zoom Web SDK Widget"] div[width][height], canvas, video, ul'
) || [];
containers.forEach(el => {
const isVideo = el.tagName.toLowerCase() === 'video';
const screenCanvas = document.querySelector(
'.zoom-MuiBox-root.css-17y2x1 canvas[aria-label="Screen share"]'
);
// Video & participant container (right section)
const videoContainer = document.querySelector('.css-137s0zm');
// Skip ul/li inside Zoom popups like menus or dialogs
const isInsideZoomPopup = el.closest('.zoom-MuiPopper-root, [role="dialog"]');
if (!sharing && !isInsideZoomPopup) {
el.style.width = `${dimensions.width}px`;
el.style.height = `${dimensions.height}px`;
el.setAttribute('width', dimensions.width);
el.setAttribute('height', dimensions.height);
} else {
//screen sharing host display arrangements
if (isHost && isVideo) {
el.style.width = '';
el.style.height = '';
el.removeAttribute('width');
el.removeAttribute('height');
const controlBar = document.querySelector('div[aria-label="Screen share control bar"]');
const sharerVideo = document.getElementById("ZOOM_WEB_SDK_SHARER_CANVAS");
if (controlBar) {
controlBar.style.zIndex = '1';
}
if (sharerVideo) {
sharerVideo.style.width = "250px";
sharerVideo.style.height = "141px";
}
} else {
//screen sharing client display arrangements
//client left side portion
if (screenCanvas) {
const width = Math.floor(dimensions.width * 0.75);
const height = dimensions.height;
// Apply to canvas
screenCanvas.style.width = `${width}px`;
screenCanvas.style.height = `${height}px`;
screenCanvas.setAttribute('width', width);
screenCanvas.setAttribute('height', height);
// Also apply to parent wrapper divs
const screenShareWrapper = document.querySelector('.zoom-MuiBox-root.css-17y2x1');
const draggableWrapper = document.querySelector('.react-draggable.zoom-MuiBox-root.css-m2stew');
const parentFlexContainer = document.querySelector('.css-kdi1e6');
const widthWithExtra = width + 10;
if (screenShareWrapper) {
screenShareWrapper.style.height = `${height}px`;
screenShareWrapper.style.maxWidth = `${widthWithExtra}px`;
screenShareWrapper.style.marginLeft = '10px';
}
if (draggableWrapper) {
draggableWrapper.style.height = `${height}px`;
draggableWrapper.style.maxWidth = `${widthWithExtra}px`;
}
if (parentFlexContainer) {
parentFlexContainer.style.justifyContent = 'flex-start';
parentFlexContainer.style.alignItems = 'center';
}
}
//client right side portion
if (videoContainer) {
const width = Math.floor(dimensions.width * 0.20);
const height = Math.floor(dimensions.height * 0.3);
videoContainer.style.width = `${width}px`;
videoContainer.style.height = `${height}px`;
const liElements = videoContainer.querySelectorAll('li');
liElements.forEach(li => {
li.style.width = `${width}px`;
li.style.height = `${height}px`;
li.style.top = '0';
li.style.left = '0';
li.style.padding = '0';
});
const childSelectors = ['canvas', 'video', 'ul'];
childSelectors.forEach(selector => {
const elements = videoContainer.querySelectorAll(selector);
elements.forEach(el => {
el.style.width = `${width}px`;
el.style.height = `${height}px`;
el.setAttribute('width', width);
el.setAttribute('height', height);
});
});
}
}
}
});
};
const observer = new MutationObserver(() => {
enforceZoomSize();
});
observer.observe(document.body, { childList: true, subtree: true });
enforceZoomSize();
return () => observer.disconnect();
}, [hasInitialized, dimensions, sharing]);
//for top and bottom space arrangements of video screen when starting
useEffect(() => {
if (!hasInitialized || !dimensions.height) return;
if (!wrapperRef.current || !isBigPopup) return;
console.log(“55555555555555555555555”);
const { clientWidth, clientHeight } = wrapperRef.current;
const verticalSpace = Math.max(0, (clientHeight - dimensions.height - LIVE_TOTAL_CONTROL_BAR_HEIGHT) / 2);
const horizontalSpace = Math.max(0, (clientWidth - dimensions.width) / 2);
const tabPanel = document.querySelector('[id^="suspension-view-tabpanel-gallery-limited"]');
if (tabPanel) {
tabPanel.style.margin = `${verticalSpace}px ${horizontalSpace}px 0px ${horizontalSpace}px`;
}
const controlBarWrapper = document.querySelector('.zoom-MuiPaper-root.css-woj72k');
if (controlBarWrapper) {
controlBarWrapper.style.marginBottom = `${verticalSpace}px`;
}
}, [hasInitialized, dimensions]);
//for end webinar button click
const observeZoomButtons = () => {
if (!meetingRef.current || !isHost) return;
observerRef.current = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
const buttons = document.querySelectorAll('button');
buttons.forEach((btn) => {
if (btn.innerText.trim().toLowerCase() === 'end webinar') {
btn.removeEventListener('click', handleEndWebinarClick);
btn.addEventListener('click', handleEndWebinarClick);
}
});
}
}
});
observerRef.current.observe(document.body, {
childList: true,
subtree: true,
});
};
const disconnectObserver = () => {
if (observerRef.current) {
observerRef.current.disconnect();
observerRef.current = null;
}
};
const handleEndWebinarClick = () => {
if (onEndWebinar) onEndWebinar();
};
const detectScreenShare = () => {
let wasSharing = false;
shareObserverRef.current = new MutationObserver(() => {
if (isHost) {
// Host detection: Check for the control bar visibility
const controlBar = document.querySelector('div[aria-label="Screen share control bar"]');
if (!controlBar) return;
const display = window.getComputedStyle(controlBar).display;
const isCurrentlySharing = display !== 'none';
if (isCurrentlySharing && !wasSharing) {
wasSharing = true;
setSharing(true);
} else if (!isCurrentlySharing && wasSharing) {
wasSharing = false;
setSharing(false);
}
} else {
// Client detection: Check for "You are viewing [name]'s screen"
const viewingBanner = document.querySelector(
'.zoom-MuiBox-root.css-1l32vgz .zoom-MuiTypography-root[title]'
);
const isCurrentlySharing = !!viewingBanner;
if (isCurrentlySharing && !wasSharing) {
wasSharing = true;
setSharing(true);
} else if (!isCurrentlySharing && wasSharing) {
wasSharing = false;
setSharing(false);
}
}
});
shareObserverRef.current.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style', 'class', 'aria-label']
});
};
const disconnectShareObserver = () => {
if (shareObserverRef.current) {
shareObserverRef.current.disconnect();
shareObserverRef.current = null;
}
};
const hideChatButtonIfNotHost = () => {
if (isHost) return; // Only hide if not host
const observer = new MutationObserver(() => {
const chatButtons = document.querySelectorAll('button[aria-label="Chat"]');
chatButtons.forEach(btn => {
btn.style.display = 'none';
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
// Optionally disconnect when component unmounts or onEnd
return () => observer.disconnect();
};
const getSignature = async () => {
try {
console.log(“called getSignature”);
const response = await AccountService.getSignature(account.id, user.id, meetingNumber, role);
const signature = response.data.data;
startMeeting(signature);
} catch (error) {
console.error('Signature fetch failed:', error);
}
};
const startMeeting = async (signature) => {
try {
const meetingSDKElement = document.getElementById(‘meetingSDKElement’);
await client.current.init({
zoomAppRoot: meetingSDKElement,
language: 'en-US',
patchJsMedia: true,
customize: {
meetingInfo: ['topic', 'host'],
video: {
isResizable: false,
popper: {
disableDraggable: true,
},
viewSizes: {
default: {
}
}
},
share: {
isResizable: true,
popper: {
disableDraggable: false,
}
}
}
});
const joinParams = {
sdkKey,
signature,
meetingNumber,
password,
userName,
userEmail,
};
if (isHost) joinParams.zak = zak;
await client.current.join(joinParams);
observeZoomButtons();
detectScreenShare();
const cleanupChatObserver = hideChatButtonIfNotHost();
setHasInitialized(true);
return () => {
cleanupChatObserver?.();
};
} catch (err) {
console.error('Error joining meeting:', err);
setHasInitialized(false);
setIsReady(false);
}
};
return (
{(!hasInitialized || !isReady) && (
)}
<div
ref={meetingRef}
id="meetingSDKElement"
className={`${styles.zoomMeetingRef} ${hasInitialized ? styles.active : ''}`}
/>
</div>
);
};
export default ZoomWebinar;