ReferenceError when trying to add logic to a Fresh page

483 views Asked by At

I'm trying to create a website with the Fresh framework, in particular I was trying to put a simple drop-down feature of a button inside a navbar, but I'm not sure where to put the code needed. I've try to create a class inside the index.tsx file and initialize it inside the export default function Home(), but the only thing that I get is a ReferenceError because document is not defined.

index.tsx

/** @jsx h */
import { h } from "preact";
import HeaderWithLogin from "../islands/HeaderWithLogin.tsx";
import Navbar from "../islands/Navbar.tsx";

class Dropdown {
    private _targetElement: Element | null;
    private _triggerElement: Element | null;
    private _visible: boolean;

    constructor(
        targetElement: Element | null = null,
        triggerElement: Element | null = null
    ) {
        this._targetElement = targetElement;
        this._triggerElement = triggerElement;
        this._visible = false;
        this._init();
    }

    _init() {
        if (this._triggerElement) {
            this._triggerElement.addEventListener("click", () => {
                this.toggle();
            });
        }
    }

    _handleClickOutside(ev: MouseEvent) {
        const clickedE = ev.target as Element;
        if (
            clickedE !== this._targetElement &&
            !(this._targetElement as Element).contains(clickedE) &&
            !this._triggerElement?.contains(clickedE) &&
            this._visible
        ) {
            this.hide();
        }
        document.body.removeEventListener(
            "click",
            this._handleClickOutside,
            true
        );
    }

    toggle() {
        if (this._visible) {
            this.hide();
            document.body.removeEventListener(
                "click",
                this._handleClickOutside,
                true
            );
        } else {
            this.show();
        }
    }

    show() {
        this._targetElement?.classList.remove("hidden");
        this._targetElement?.classList.add("block");

        document.body.addEventListener("click", this._handleClickOutside, true);

        this._visible = true;
    }

    hide() {
        this._targetElement?.classList.remove("block");
        this._targetElement?.classList.add("hidden");

        this._visible = false;
    }
}

function initDropdown() {
    document
        .querySelectorAll("[data-dropdown-toggle]")
        .forEach((triggerElement) => {
            const targetElement: Element | null = document.getElementById(
                triggerElement.getAttribute("data-dropdown-toggle") as string
            );

            new Dropdown(targetElement, triggerElement);
        });
}

export default function Home() {
    if (document.readyState !== 'loading')  {
        initDropdown();
    }   else    {
        document.addEventListener('DOMContentLoaded', initDropdown);
    }
    return (
        <body>
            <Navbar></Navbar>
        </body>
    );
}

Navbar.tsx

/** @jsx h */
import { h } from "preact";
import { tw } from "@twind";

export default function Navbar()    {
    return (
        <nav>
            <ul>
                <li>
                    <button data-dropdown-toggle="dropdownElement">Dropdown Element</button>
                    <div id="dropdownElement">
                        <ul>
                            <li>
                                <a href="/">Link To Other Page</a>
                            </li>
                        </ul>
                    </div>
                </li>
                <li>
                    <button>Button 1</button>
                </li>
                <li>
                    <a href="/">Button 2</a>
                </li>
                <li>
                    <a href="/">Button 3</a>
                </li>
            </ul>
            <div>
                <input type="text" id="search-navbar" class={tw`block p-2 pl-10 w-full text-gray-900 bg-gray-50 rounded-lg border border-gray-300 sm:text-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500`} placeholder="Search..."></input>
            </div>
        </nav>
    );
}

Error given:

An error occured during route handling or page rendering.

ReferenceError: document is not defined
    at Object.Home (file:///.../routes/index.tsx:89:5)
    at h (https://esm.sh/v87/[email protected]/X-ZC9wcmVhY3RAMTAuOC4y/deno/preact-render-to-string.js:4:1003)
    at h (https://esm.sh/v87/[email protected]/X-ZC9wcmVhY3RAMTAuOC4y/deno/preact-render-to-string.js:4:1103)
    at h (https://esm.sh/v87/[email protected]/X-ZC9wcmVhY3RAMTAuOC4y/deno/preact-render-to-string.js:4:1103)
    at h (https://esm.sh/v87/[email protected]/X-ZC9wcmVhY3RAMTAuOC4y/deno/preact-render-to-string.js:4:1103)
    at h (https://esm.sh/v87/[email protected]/X-ZC9wcmVhY3RAMTAuOC4y/deno/preact-render-to-string.js:4:1103)
    at m (https://esm.sh/v87/[email protected]/X-ZC9wcmVhY3RAMTAuOC4y/deno/preact-render-to-string.js:3:684)
    at render (https://deno.land/x/[email protected]/src/server/render.tsx:180:16)
    at Object.render [as renderFn] (file:///.../main.ts:20:3)
    at render (https://deno.land/x/[email protected]/src/server/render.tsx:184:14)

How can I resolve this error? I believe that including the code needed in a script tag would be enough, but I'm curious to know if there are other ways to incorporate code inside the pages or if that's the best way to do it.

1

There are 1 answers

2
rschristian On BEST ANSWER

document is a browser global. It does not exist in environments outside of the browser.

You will need to add a check rather than assuming document is universally defined.

if (typeof document !== 'undefined') {
  // use document
}

Really though you shouldn't be using browser globals like that at all with UI frameworks like Preact. You should be using refs with wrapping elements if need be.