TahaSh / swapy

✨ A framework-agnostic tool that converts any layout into a drag-to-swap one with just a few lines of code https://swapy.tahazsh.com/
MIT License
4.84k stars 82 forks source link

Dynamic content being not rendered "Failed to execute 'removeChild' on 'Node'" #37

Open pedrosouza458 opened 2 weeks ago

pedrosouza458 commented 2 weeks ago

im testing the lib with dynamic content and i get this errors:

Unhandled Runtime Error
NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
1 of 1 errorNext.js (14.2.6)
Unhandled Runtime Error
TypeError: Cannot read properties of null (reading 'element')

i know its kinda messy, but i didint find much examples (just one in React) so i wanna know if this a real issue or im doing something wrong, let me now! love the lib.


"use client";

import Image from "next/image";
import { useEffect, useState } from "react";
import { createSwapy } from "swapy";
import dynamic from "next/dynamic";
import { useSearchParams } from "next/navigation";
const DEFAULT = {
  "1": "a",
  "2": "b",
  "3": "c",
  "4": "d",
  "5": "e",
};
interface WebsiteData {
  logo?: string;
  name?: string;
  title?: string;
  img?: string;
  description?: string;
}

function A({ data }: { data: WebsiteData }) {
  return (
    <div className="item a" data-swapy-item="a">
      {data ? (
        <div className="flex items-center justify-center gap-2 rounded-xl">
          <Image
            alt="teste"
            height={0}
            width={30}
            className="rounded-full"
            src={data.logo ? data.logo : ""}
          />
          <h1 className="font-semibold">{data.name}</h1>
        </div>
      ) : (
        <p>Loading...</p>
      )}
      {/* <div className="handle" data-swapy-handle></div> */}
    </div>
  );
}

function B({ data }: { data: WebsiteData }) {
  return (
    <div className="item b w-full h-full" data-swapy-item="b">
      {data ? (
        <h1 className="my-4 h-full w-full border-2 dark:border-slate-800 p-3 rounded-xl font-semibold text-xl overflow-hidden text-ellipsis whitespace-nowrap">
         {data.title}
        </h1>
      ) : (
        <p>Loading</p>
      )}
    </div>
  );
}

function C({ data }: { data: WebsiteData }) {
  return (
    <div className="item c" data-swapy-item="c">
      {data ? (
        <div className="flex-shrink-0">
          <img
            className="rounded-lg h-72 w-full object-cover"
            src={data.img}
            alt=""
          />
        </div>
      ) : (
        <p>Loading</p>
      )}
    </div>
  );
}

function D({ data }: { data: WebsiteData }) {
  return (
    <div className="item d h-full" data-swapy-item="d">
      {data ? (
        <div className="border-2 dark:border-slate-800 p-3 rounded-xl h-full">
         {data.name}
        </div>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}

function E({ data }: { data: WebsiteData }) {
  return (
    <div className="item e h-full" data-swapy-item="e">
      {data ? (
      <div className="border-2 dark:border-slate-800 rounded-xl p-3 h-full">
      Made with ❤️ by URLCard
    </div>
      ) : <p>Loading...</p>}
    </div>
  );
}

function getItemById(itemId: "a" | "b" | "c" | "d" | "e" | null) {
  const [data, setData] = useState<WebsiteData | null>(null);
  const searchParams = useSearchParams();
  const url = searchParams.get("url");

  if (!url || typeof url !== "string") {
    return <div>No valid URL provided</div>;
  }

  useEffect(() => {
    if (!url || typeof url !== "string") return;

    const fetchData = async () => {
      try {
        const response = await fetch(
          `http://localhost:8080/websites?url=${url}`
        );
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error("Failed to fetch website data:", error);
      }
    };

    fetchData();
  }, [url]); // Add `url` to dependencies to ensure proper effect trigger

  switch (itemId) {
    case "a":
      return <A data={data} />;
    case "b":
      return <B data={data} />;
    case "c":
      return <C data={data} />;
    case "d":
      return <D data={data}/>;
    case "e":
      return <E data={data}/>;
  }
}

function App() {
  // fetching

  const slotItems: Record<string, "a" | "b" | "c" | "d" | null> =
    localStorage.getItem("slotItem")
      ? JSON.parse(localStorage.getItem("slotItem")!)
      : DEFAULT;
  useEffect(() => {
    const container = document.querySelector(".container");

    if (container) {
      const observer = new MutationObserver(() => {
        // Re-initialize Swapy whenever the DOM is modified
        const swapy = createSwapy(container);

        swapy.onSwap(({ data }) => {
          localStorage.setItem("slotItem", JSON.stringify(data.object));
        });
      });

      // Observe changes to the container and its descendants
      observer.observe(container, { childList: true, subtree: true });

      // Initialize Swapy for the first time
      const swapy = createSwapy(container);

      swapy.onSwap(({ data }) => {
        localStorage.setItem("slotItem", JSON.stringify(data.object));
      });

      // Cleanup observer when the component unmounts
      return () => {
        observer.disconnect();
      };
    } else {
      console.error(
        "Container element not found or HTML structure is invalid."
      );
    }
  }, []);

  return (
    <div className="flex justify-center">
      <div className="container w-[50rem] border-2 px-4 py-4 rounded-xl dark:border-slate-800">
        <div className="slot a" data-swapy-slot="1">
          {getItemById(slotItems["1"])}
        </div>
        <div className="slot b w-full" data-swapy-slot="2">
          {getItemById(slotItems["2"])}
        </div>
        <div className="flex gap-4 rounded-xl">
          <div className="slot c w-[40%]" data-swapy-slot="3">
            {getItemById(slotItems["3"])}
          </div>
          <div className="flex-grow flex flex-col gap-3 w-[60%]">
            <div className="slot d h-full" data-swapy-slot="4">
              {getItemById(slotItems["4"])}
            </div>
            <div className="slot e" data-swapy-slot="5">
              {getItemById(slotItems["5"])}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

export default dynamic(() => Promise.resolve(App), {
  ssr: false,
});
pedrosouza458 commented 2 weeks ago

@TahaSh did you have some example using dynamic content in react/nextjs?

TahaSh commented 2 weeks ago

@pedrosouza458 I didn't test your code, but could you please update Swapy to v0.1.2 and see if it fixes it?

pedrosouza458 commented 2 weeks ago

im still having weird behavior, sometimes this error dont happen, but it still happening, this error appears usually when i return to the div that i change before, like if i change the positions, refresh the page, the error has more change happen, but it happens also when i delete the localstorage to return to the original positions, and i didint change the code, you can also test it with the dynamic data, all is in my readme.md in this repo: https://github.com/pedrosouza458/urlcard

https://github.com/user-attachments/assets/f8f3592e-5ced-4488-b742-5c99feeb3b6b

i think that this is cause the error, it can be a grid layout error, but i think its all correct [

Captura de Tela 2024-08-28 às 23 04 05

](url) also when i refresh the page, the divs that i change positions became undraggable, only dragging if i use another div to change space with it

TahaSh commented 2 weeks ago

@pedrosouza458 Thank you for providing an example. I'm currently working on a better solution for dynamic items in frameworks. I'll let you know when it's ready!

TahaSh commented 6 days ago

@pedrosouza458 Dynamic slots and items are now ready in v0.2.0. Here's a react example showing how to use it.

You can also follow this video to learn how to use it: https://www.youtube.com/watch?v=w5Vk63Yuytg