diff --git a/apps/nextjs/src/components/board/sections/dynamic-section.tsx b/apps/nextjs/src/components/board/sections/dynamic-section.tsx
index 8bb0a52c3..a8d614f98 100644
--- a/apps/nextjs/src/components/board/sections/dynamic-section.tsx
+++ b/apps/nextjs/src/components/board/sections/dynamic-section.tsx
@@ -6,6 +6,7 @@ import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
import type { DynamicSectionItem } from "~/app/[locale]/boards/_types";
import { BoardDynamicSectionMenu } from "./dynamic/dynamic-menu";
import { GridStack } from "./gridstack/gridstack";
+import { useSectionContext } from "./section-context";
import classes from "./item.module.css";
interface Props {
@@ -15,6 +16,7 @@ interface Props {
export const BoardDynamicSection = ({ section }: Props) => {
const board = useRequiredBoard();
const currentLayoutId = useCurrentLayout();
+ const { ancestorSectionIds } = useSectionContext();
const options = section.options;
return (
@@ -53,7 +55,12 @@ export const BoardDynamicSection = ({ section }: Props) => {
)}
{/* Use unique key by layout to reinitialize gridstack */}
-
+
diff --git a/apps/nextjs/src/components/board/sections/gridstack/gridstack.spec.ts b/apps/nextjs/src/components/board/sections/gridstack/gridstack.spec.ts
new file mode 100644
index 000000000..3df5c1a38
--- /dev/null
+++ b/apps/nextjs/src/components/board/sections/gridstack/gridstack.spec.ts
@@ -0,0 +1,11 @@
+import { describe, expect, test } from "vitest";
+
+import { filterRecursiveInnerSections } from "./gridstack";
+
+describe("filterRecursiveInnerSections", () => {
+ test("removes sections that would recursively render an ancestor", () => {
+ const innerSections = [{ id: "safe" }, { id: "parent" }, { id: "root" }];
+
+ expect(filterRecursiveInnerSections(innerSections, new Set(["root", "parent"]))).toEqual([{ id: "safe" }]);
+ });
+});
diff --git a/apps/nextjs/src/components/board/sections/gridstack/gridstack.tsx b/apps/nextjs/src/components/board/sections/gridstack/gridstack.tsx
index e851e5c2a..1d0090625 100644
--- a/apps/nextjs/src/components/board/sections/gridstack/gridstack.tsx
+++ b/apps/nextjs/src/components/board/sections/gridstack/gridstack.tsx
@@ -12,16 +12,21 @@ import { useGridstack } from "./use-gridstack";
interface Props extends BoxProps {
section: Exclude | DynamicSectionItem;
+ ancestorSectionIds?: ReadonlySet;
}
-export const GridStack = ({ section, ...props }: Props) => {
+export const GridStack = ({ section, ancestorSectionIds = new Set(), ...props }: Props) => {
const { items, innerSections } = useSectionItems(section.id);
- const itemIds = [...items, ...innerSections].map((item) => item.id);
+ const nextAncestorSectionIds = new Set(ancestorSectionIds).add(section.id);
+ const safeInnerSections = filterRecursiveInnerSections(innerSections, nextAncestorSectionIds);
+ const itemIds = [...items, ...safeInnerSections].map((item) => item.id);
const { refs } = useGridstack(section, itemIds);
return (
-
+
{
);
};
+
+export const filterRecursiveInnerSections = (
+ innerSections: T[],
+ ancestorSectionIds: ReadonlySet,
+) => innerSections.filter((innerSection) => !ancestorSectionIds.has(innerSection.id));
diff --git a/apps/nextjs/src/components/board/sections/section-context.ts b/apps/nextjs/src/components/board/sections/section-context.ts
index c01d05d7c..583a07d48 100644
--- a/apps/nextjs/src/components/board/sections/section-context.ts
+++ b/apps/nextjs/src/components/board/sections/section-context.ts
@@ -5,6 +5,7 @@ import type { UseGridstackRefs } from "./gridstack/use-gridstack";
interface SectionContextProps {
section: Exclude | DynamicSectionItem;
+ ancestorSectionIds: ReadonlySet;
innerSections: DynamicSectionItem[];
items: SectionItem[];
refs: UseGridstackRefs;