calcom / cal.com

Scheduling infrastructure for absolutely everyone.
https://cal.com
Other
32.42k stars 8.01k forks source link

[CAL-3893] Error after build: iframe doesn't exist. `createIframe` must be called before `doInIframe` #15357

Open fabriziospadaro opened 5 months ago

fabriziospadaro commented 5 months ago

Bug Report: iframe doesn't exist. createIframe must be called before doInIframe

Issue Summary

When embedding an iframe in a Next.js page, an error occurs after building the page: iframe doesn't exist. createIframe must be called before doInIframe. This issue does not occur during development (npm run dev). Note that the iframe embed still works, and all the functionalities are preserved.

Steps to Reproduce

  1. Create a Next.js page and embed an iframe using @calcom/embed-react.
  2. Use the following code snippet in your component:

    "use client"
    
    import Cal, { getCalApi } from "@calcom/embed-react";
    import { useEffect } from "react";
    
    export default function CalWidget({ calLink }) {
      useEffect(() => {
        (async function () {
          const cal = await getCalApi();
          cal("ui", {
            "theme": "light",
            "styles": { "branding": { "brandColor": "#2B5ADC" } },
            "hideEventTypeDetails": false,
            "layout": "month_view",
          });
        })();
      }, [])
    
      return (
        <Cal
          id="book"
          onScrollCapture={(e) => {
            e.stopPropagation();
          }}
          calLink={calLink}
        />
      )
    }
  3. Run the development server using npm run dev and verify that the page works as expected.
  4. Build the project using npm run build and then start the server using npm start.
  5. Navigate to the page and observe the error message.

Actual Results

Expected Results

CAL-3893

dosubot[bot] commented 5 months ago

Hey @fabriziospadaro! I'm here to help you out with any bugs, questions, or contributions you have. Let's tackle this together!

To resolve the error iframe doesn't exist. createIframe must be called before doInIframe, you need to ensure that the createIframe method is called before the doInIframe method. This error occurs because the doInIframe method requires the iframe to be created and ready before it can execute its instructions.

Here is a simplified example of how you can ensure that createIframe is called before doInIframe:

// Ensure that createIframe is called first
const cal = new Cal('your-namespace', []);
cal.createIframe({
  calLink: 'your-cal-link',
  queryObject: {},
  calOrigin: 'your-cal-origin'
});

// Now you can safely call doInIframe
cal.doInIframe({
  method: 'yourMethod',
  arg: 'yourArgument'
});

In the context of using @calcom/embed-react, make sure that the iframe is created during the component's lifecycle before any operations that require the iframe are performed. For example, you can use the useEffect hook in a React component to ensure the iframe is created when the component mounts:

import React, { useEffect } from 'react';
import { Cal } from '@calcom/embed-react';

const YourComponent = () => {
  useEffect(() => {
    const cal = new Cal('your-namespace', []);
    cal.createIframe({
      calLink: 'your-cal-link',
      queryObject: {},
      calOrigin: 'your-cal-origin'
    });

    // Now you can safely call doInIframe
    cal.doInIframe({
      method: 'yourMethod',
      arg: 'yourArgument'
    });
  }, []);

  return (
    <div>
      {/* Your component JSX */}
    </div>
  );
};

export default YourComponent;

Additionally, ensure that the iframe is properly initialized and set up. According to the embed-iframe-init.ts file from Cal.com, the EmbedInitIframe function is responsible for setting up the iframe and ensuring it is ready for interaction. This function checks for the namespace in the query parameters or window.name, sets the isEmbed flag, and applies CSS variables for the theme. This initialization is crucial for the iframe's readiness.

By ensuring that createIframe is called before doInIframe and that the iframe is properly initialized using the EmbedInitIframe function, you can avoid the error and ensure that the iframe is properly set up before any operations are performed on it [1][2][3].

To continue talking to Dosu, mention @dosu.

alisayed95 commented 5 months ago

I'm facing the same issue and I'm not able to solve. any help here?

alisayed95 commented 5 months ago

@dosu the proposed solution is not working

wallawe commented 4 months ago

@dosu @dosubot you left code that is irrelevant to the example provided. the snippet we are given as developers is as the OP mentioned:

  useEffect(() => {
    (async function () {
      const cal = await getCalApi();
      cal("ui", {
        "theme": "light",
        "styles": { "branding": { "brandColor": "#2B5ADC" } },
        "hideEventTypeDetails": false,
        "layout": "month_view",
      });
    })();
  }, [])

there are no createIframe or doInIframe methods being used or provided in the snippet that cal.com gives us.

fabriziospadaro commented 1 month ago

Any update for this? My sentry is going crazy 😮‍💨

dosubot[bot] commented 1 month ago

To address the issue with the error message "iframe doesn't exist. createIframe must be called before doInIframe" in the context of using the @calcom/embed-react package, it's important to ensure that the iframe is fully initialized before any operations are performed on it. The getCalApi function is designed to handle this initialization process.

Here's a refined approach using the getCalApi function:

  1. Ensure Initialization: The getCalApi function should be called to initialize the Cal API, which will handle the iframe setup. Make sure this is done before any UI operations.

  2. Check for Readiness: Use the event-driven approach to ensure the iframe is ready. The package listens for the __iframeReady event to confirm the iframe is ready for operations.

Here's how you can adjust your code:

import Cal, { getCalApi } from "@calcom/embed-react";
import { useEffect } from "react";

export default function CalWidget({ calLink }) {
  useEffect(() => {
    (async function () {
      const cal = await getCalApi();
      // Ensure the iframe is ready before performing UI operations
      cal.on("__iframeReady", () => {
        cal("ui", {
          "theme": "light",
          "styles": { "branding": { "brandColor": "#2B5ADC" } },
          "hideEventTypeDetails": false,
          "layout": "month_view",
        });
      });
    })();
  }, []);

  return (
    <Cal
      id="book"
      onScrollCapture={(e) => {
        e.stopPropagation();
      }}
      calLink={calLink}
    />
  );
}

This approach ensures that the UI operations are only executed once the iframe is fully initialized and ready, preventing the error from occurring.

To continue talking to Dosu, mention @dosu.

fabriziospadaro commented 1 month ago

@dosu The correct api to use should be:

cal("on", {
        action: "__iframeReady",
        callback: (e) => {
          cal("ui",
            {
              "theme": "light",
              "styles": {
                "branding": { "brandColor": "#2B5ADC" },
              },
              "layout": "month_view",
            });
        }
      });

but this still doesn't fix the issue.

dosubot[bot] commented 1 month ago

To address the issue with the error message "iframe doesn't exist. createIframe must be called before doInIframe" when using the @calcom/embed-react package, it's crucial to ensure that the iframe is fully initialized before performing any operations on it. The __iframeReady event is key to confirming the iframe's readiness.

Here's how you can adjust your code to ensure the iframe is ready before executing UI operations:

import Cal, { getCalApi } from "@calcom/embed-react";
import { useEffect } from "react";

export default function CalWidget({ calLink }) {
  useEffect(() => {
    (async function () {
      const cal = await getCalApi();
      // Use the correct API to listen for the iframe readiness
      cal("on", {
        action: "__iframeReady",
        callback: () => {
          cal("ui", {
            "theme": "light",
            "styles": {
              "branding": { "brandColor": "#2B5ADC" },
            },
            "layout": "month_view",
          });
        }
      });
    })();
  }, []);

  return (
    <Cal
      id="book"
      onScrollCapture={(e) => {
        e.stopPropagation();
      }}
      calLink={calLink}
    />
  );
}

This setup ensures that the UI configuration is applied only after the __iframeReady event is triggered, indicating that the iframe is fully initialized and ready for interaction. If the issue persists, double-check that the getCalApi function is correctly initializing the API and that the event listener is properly set up to handle the __iframeReady event.

To continue talking to Dosu, mention @dosu.

hershaw commented 1 week ago

I was getting this error both in dev and the built server but with all functionality working. Looking at the iframe after rendering, I see the the following:

<iframe class="cal-embed" name="cal-embed=" .../>

with the name attribute looking like there's something missing on the name=.

Not even sure this is the relevant code but it looks like it should be set to namespace.

So I added the namespace parameter to my code and the error went away:

useEffect(() => {
    (async function () {
      const cal = await getCalApi({ namespace: "nynamespace" });
      cal("ui", {
        theme: "light",
        styles: {
          branding: { brandColor: "white" },
        },
      });
    })();
  }, []);

Buuuuuuut the iframe still has name="cal-embed=".

@dosu what do you think?