Zoom Web Meeting SDK Issue: Rejoin Fails – Host UI Misplaced, Participant Blank Screen

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.

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:

  1. Join webinar (host and participant) via the SDK.
  2. Leave the webinar using client.leaveMeeting().
  3. Attempt to rejoin the same webinar.
  4. 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) && (


Joining Live…


)}

  <div
    ref={meetingRef}
    id="meetingSDKElement"
    className={`${styles.zoomMeetingRef} ${hasInitialized ? styles.active : ''}`}
  />

</div>

);
};

export default ZoomWebinar;