From 1154b06d52d24ab307305787d5698dff67e8d1fd Mon Sep 17 00:00:00 2001 From: Mahmoud Moravej <55846417+mahmoudmoravej@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:13:54 -0500 Subject: [PATCH] resolve data binding for not defined elements (#16) --- .changeset/nervous-mails-approve.md | 5 ++++ packages/r2wc/src/WebComponentHTMLElement.tsx | 30 ++++++++++++++++++- samples/react/public/index.html | 2 -- samples/react/public/index.js | 23 +++++++++++--- samples/react/src/Layout.tsx | 2 +- samples/react/src/MainPage.tsx | 2 +- samples/react/src/StorePage.tsx | 2 +- 7 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 .changeset/nervous-mails-approve.md diff --git a/.changeset/nervous-mails-approve.md b/.changeset/nervous-mails-approve.md new file mode 100644 index 0000000..be7cfb8 --- /dev/null +++ b/.changeset/nervous-mails-approve.md @@ -0,0 +1,5 @@ +--- +"@workleap/r2wc": patch +--- + +Fix a bug: Resolve web elements "data" properties even if they have been set sooner than the element gets defined. diff --git a/packages/r2wc/src/WebComponentHTMLElement.tsx b/packages/r2wc/src/WebComponentHTMLElement.tsx index 94806fd..80a4e08 100644 --- a/packages/r2wc/src/WebComponentHTMLElement.tsx +++ b/packages/r2wc/src/WebComponentHTMLElement.tsx @@ -56,7 +56,35 @@ export class WebComponentHTMLElementBase extends HTMLElement { } export class WebComponentHTMLElement extends WebComponentHTMLElementBase { - #props = new Observable(); + #props : Observable; + + constructor() { + super(); + + let alreadySetData: Props | undefined = undefined; + + try { + //if the data is set before the element is defined in the DOM, the getter function will not override it! + //so, we read the data to initialize the props with it and then remove it. + //Removing it causes the getter to work as expected afterwards. + //There are different reasons that causes the element to be defined in the DOM after the data is set. + // 1. The script is loaded after the data is set due to network issues. + // 2. The script is loaded after the data is set due to the script being loaded lately for any reason. + + alreadySetData = this.data; + delete this.data; + } catch { + //the error happens in regular cases when this.data is not set. + //in this situation, the #props has not been initialized yet and the getter function is being called which causes the error. + //so, we catch the error and ignore it. + } + + if (alreadySetData) { + console.warn("The data has been set before the element is defined in the DOM. This means a potenial of delay rendering. Please make sure to load the widget script before setting the data."); + } + + this.#props = new Observable(alreadySetData); + } protected get reactComponent(): React.ComponentType { throw new Error("You must implement this method in a subclass."); diff --git a/samples/react/public/index.html b/samples/react/public/index.html index 8bb03dc..5b982ac 100644 --- a/samples/react/public/index.html +++ b/samples/react/public/index.html @@ -5,8 +5,6 @@ React app - - diff --git a/samples/react/public/index.js b/samples/react/public/index.js index 9ac870a..863b398 100644 --- a/samples/react/public/index.js +++ b/samples/react/public/index.js @@ -1,5 +1,20 @@ -import { MovieWidgets } from "/cdn/movie-widgets/index.js"; -MovieWidgets.initialize({ - theme: document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light" -}); +const head = document.getElementsByTagName("head")[0]; + +const link = document.createElement("link"); +link.rel = "modulepreload"; +link.href = "/cdn/movie-widgets/index.js"; + +head.insertBefore(link, head.firstChild); + +const script = document.createElement("script"); +script.src = "/cdn/movie-widgets/index.js"; +script.type = "module"; +script.onload = () => { + window.MovieWidgets.initialize({ + theme: document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light" + }); +}; + +head.appendChild(script); + diff --git a/samples/react/src/Layout.tsx b/samples/react/src/Layout.tsx index c61022e..81abbfc 100644 --- a/samples/react/src/Layout.tsx +++ b/samples/react/src/Layout.tsx @@ -1,7 +1,7 @@ import { Button, Div, Flex } from "@workleap/orbiter-ui"; import { Link, Outlet } from "react-router-dom"; -import { MovieFinder, SelectedMovie } from "../../widgets/movie-widgets/dist/react/index.js"; +import { MovieFinder, SelectedMovie } from "@samples/movie-widgets/react"; import { AppContextProvider, useAppContext } from "./AppContext.tsx"; diff --git a/samples/react/src/MainPage.tsx b/samples/react/src/MainPage.tsx index d11eacb..b006ac3 100644 --- a/samples/react/src/MainPage.tsx +++ b/samples/react/src/MainPage.tsx @@ -1,6 +1,6 @@ +import { MovieDetails, MoviePopup, SelectedMovie, Ticket } from "@samples/movie-widgets/react"; import { Flex } from "@workleap/orbiter-ui"; import { useState } from "react"; -import { MovieDetails, MoviePopup, SelectedMovie, Ticket } from "../../widgets/movie-widgets/dist/react/index.js"; export function MainPage() { const [boughtTickets, setBoughtTickets] = useState<{ key: string; count:number; title: string }[]>([]); diff --git a/samples/react/src/StorePage.tsx b/samples/react/src/StorePage.tsx index 0f61d1b..3d7ed9d 100644 --- a/samples/react/src/StorePage.tsx +++ b/samples/react/src/StorePage.tsx @@ -1,6 +1,6 @@ +import { MovieDetails, SelectedMovie } from "@samples/movie-widgets/react"; import { Checkbox } from "@workleap/orbiter-ui"; import { useState } from "react"; -import { MovieDetails, SelectedMovie } from "../../widgets/movie-widgets/dist/react/index.js"; export function StorePage() { const [showRanking, setShowRanking] = useState(false);