fix(base): scope per-base UI atoms by pageId to prevent embed render loops

Two BaseTable instances on the same page (e.g. multiple base embeds in
one document) shared the same global jotai atoms for activeViewId,
editingCell, property menu state, and row selection. Each instance's
useEffect that synced activeViewId would clobber the other's value
every render, pinning React into a "Maximum update depth exceeded"
loop.

Convert every UI atom in base-atoms.ts to an atomFamily keyed by pageId
so each base owns its own scope, and thread pageId through the grid
component tree (GridRow, GridCell, GridHeaderCell, RowNumberCell,
RowNumberHeaderCell, PropertyMenuContent) plus useRowSelection so each
consumer reaches the per-base atom. use-base-socket already had pageId
in scope; its store.get/store.set calls now resolve through
selectedRowIdsAtomFamily(pageId) too.
This commit is contained in:
Philipinho
2026-04-27 21:35:39 +01:00
parent cd8d1e0ed8
commit bfa85b9835
14 changed files with 96 additions and 54 deletions
@@ -24,7 +24,7 @@ import {
useCreateViewMutation,
useUpdateViewMutation,
} from "@/features/base/queries/base-view-query";
import { activeViewIdAtom } from "@/features/base/atoms/base-atoms";
import { activeViewIdAtomFamily } from "@/features/base/atoms/base-atoms";
import { useBaseTable } from "@/features/base/hooks/use-base-table";
import { useRowSelection } from "@/features/base/hooks/use-row-selection";
import useCurrentUser from "@/features/user/hooks/use-current-user";
@@ -53,7 +53,7 @@ export function BaseTable({ pageId, embedded }: BaseTableProps) {
useBaseSocket(pageId);
const { data: base, isLoading: baseLoading, error: baseError } = useBaseQuery(pageId);
const [activeViewId, setActiveViewId] = useAtom(activeViewIdAtom) as unknown as [string | null, (val: string | null) => void];
const [activeViewId, setActiveViewId] = useAtom(activeViewIdAtomFamily(pageId)) as unknown as [string | null, (val: string | null) => void];
const views = base?.views ?? [];
const activeView = useMemo(() => {
@@ -146,7 +146,7 @@ export function BaseTable({ pageId, embedded }: BaseTableProps) {
}
}, [activeView, activeViewId, setActiveViewId]);
const { clear: clearSelection } = useRowSelection();
const { clear: clearSelection } = useRowSelection(pageId);
useEffect(() => {
clearSelection();
}, [pageId, activeView?.id, clearSelection]);