Workaround for Next.JS error "referenceError: document is not defined" for an external library component?
Asked Answered
C

2

8

I am having some troubles integrating a component from the outside library 'react-calendly', the PopUpButton widget specifically, that requires the parent DOM node to be inserted into. My current understanding is that my issue is being caused by the fact that Next.js uses SSR and therefore I do not have access to the browser. How can I work around this? For reference, my website is a very simple fullstack app for his business and I am new to React/full-stack development. Here is the code for the app portion that renders my page components:

import '../styles/globals.css'
import styles from '../styles/App.module.css'
import Navbar from '../components/navbar'
import Footer from '../components/footer'

function MyApp({Component, pageProps}) {

  return (
    <div className={styles.app}>
      <div>
        <Navbar />
      </div>
      <div className={styles.body} id="body">
        <Component props={pageProps} />
      </div>
      <div>
        <Footer className={styles.footer}/>
      </div>
    </div>
  )
}

export default MyApp

And here is the code for my specific page component:

import styles from '../styles/Home.module.css'
import Head from 'next/head'
import { PopupButton } from 'react-calendly'

export default function Home() {
  
  return (
    <div className="home">
      <Head>
        <title>Homepage</title>
      </Head>
      <div className="cal_div">

        <PopupButton
          url="https://calendly.com/my_url"
          rootElement={document.getElementsById("body")}
          text="Click here to schedule!"
        />
      </div>
    </div> 
  )
}

Crucial answered 25/7, 2022 at 3:12 Comment(3)
Sorry, but I still get the same error even after using dynamic import. Any ideas on what this could be? I followed the syntax in the question you linked, but still can't seem to get it to work.Crucial
Yeah, the rootElement={document.getElementsById("body")} is also an issue, and can't run during server-side rendering. You'll have to move the PopupButton to a new custom component entirely, and dynamically load that component instead. Check this example I've create in codesandbox: codesandbox.io/s/upbeat-almeida-f8zv3l.Rowley
Also, getElementsById should be getElementById.Rowley
S
7

I actually managed to fix this issue for the exact same case next.js and react.js

The architecture is as follow:

1. Calendly calling react component -> 2. dynamic calendly __next component -> 3. calendly child

1. The React Parent Component:

'use client';
import React from "react";
import CalendlyDynamic from "./Components/CalendlyDynamic";

export default function Home(){

return (
        <div>
             <CalendlyDynamic />
             <div id="__next"></div>
        </div>
)}

1. -> 2. The Calendly dynamic Component:

import dynamic from "next/dynamic";

const Calendly = dynamic(() => import("../Components/Calendly"), {
  ssr: false
});


export default function Home() {
  return (
    <div>
      <Calendly />
    </div>
  );
}

2. -> 3. The Calendly Child

'use client'
import { PopupButton } from "react-calendly";
import { useEffect, useState } from "react";

export default function Calendly() {
  const [rootElement, setRootElement] = useState(null);

  useEffect(() => {
    // Wait for the component to be mounted before setting the rootElement
    if (typeof window !== "undefined") {
      setRootElement(document.getElementById("__next"));
    }
  }, []);

  return (
    <div className="cal_div">
      <PopupButton
        className="rounded-md bg-primary py-4 px-8 text-base font-semibold text-white duration-300 ease-in-out hover:bg-primary/80"
        url="https://calendly.com/your-link"
        rootElement={rootElement}
        text="Schedule Appointment"
      />
    </div>
  );
}
Sorcery answered 26/10, 2023 at 18:41 Comment(2)
Worked like a charm :)) THANK YOU!Biron
Can confirm that this worked for me in Next 14 with App Router.Barroom
T
0

Props to the Verified Answer. I didn't fully understand it but I played around with the concept and I got it working with some slight differences.

Calendly Reusable Button Component

"use client"
// calendlyDynamic.tsx

import { PopupButton } from "react-calendly"
import { useEffect, useState } from "react"

type CalendlyButtonProps = {
    props: {
        label: string
        buttonLink: string
        className?: string
    }
}

export default function CalendlyButton({ props }: CalendlyButtonProps) {
    const [rootElement, setRootElement] = useState<HTMLElement | null>(null)
    const { buttonLink, label, className } = props

    useEffect(() => {
        // Wait for the component to be mounted before setting the rootElement
        if (typeof window !== "undefined") {
            setRootElement(document.getElementById("__next"))
        }
    }, [])

    return (
        <div className="cal_div">
            <PopupButton
                className={`rounded-md py-1.5 text-lg font-light w-full duration-300 ease-in-out ${className}`}
                url={buttonLink}
                rootElement={rootElement || document.body}
                text={label}
            />
        </div>
    )
}

Root Layout

// layout.tsx

import CalendlyButton from "@/components/myComponents/CalendlyDynamic"
export default async function MainProjectLayout({
    children,
}: Readonly<{
    children: React.ReactNode
}>) {
    return (
        <html lang="en" suppressHydrationWarning>
            <body
                id="__next"
                className={`relative`}>
               <NextTopLoader color="#FFBF23" />
                {children}
            </body>
        </html>
)}

USE

// component that you want to include the button in.

<CalendlyButton
    props={{
       label: "Schedule Repair",
       className: "bg-amber-400 text-white p-2 py-1 hover:bg-amber-500 transition-colors duration-75 ease-in",
       buttonLink: calendlyUrls.repair,
    }}
/>
Terrier answered 23/9 at 20:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.