fix(tree): update sidebar-pages cache directly instead of refetching on page move (#1870)

This commit is contained in:
Philip Okugbe
2026-01-25 12:38:44 +00:00
committed by GitHub
parent 1ca7d42203
commit 351b075ebb
4 changed files with 150 additions and 16 deletions
@@ -163,9 +163,6 @@ export function useDeletePageMutation() {
export function useMovePageMutation() { export function useMovePageMutation() {
return useMutation<void, Error, IMovePage>({ return useMutation<void, Error, IMovePage>({
mutationFn: (data) => movePage(data), mutationFn: (data) => movePage(data),
onSuccess: () => {
invalidateOnMovePage();
},
}); });
} }
@@ -458,17 +455,127 @@ export function invalidateOnUpdatePage(
}); });
} }
export function invalidateOnMovePage() { export function updateCacheOnMovePage(
//for move invalidate all sidebars for now (how to do???) spaceId: string,
//invalidate all root sidebar pages pageId: string,
queryClient.invalidateQueries({ oldParentId: string | null,
queryKey: ["root-sidebar-pages"], newParentId: string | null,
}); pageData: Partial<IPage>,
//invalidate all sub sidebar pages ) {
queryClient.invalidateQueries({ // Remove page from old parent's cache
queryKey: ["sidebar-pages"], const oldQueryKey =
}); oldParentId === null
// --- ? ["root-sidebar-pages", spaceId]
: ["sidebar-pages", { pageId: oldParentId, spaceId }];
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(
oldQueryKey,
(old) => {
if (!old) return old;
return {
...old,
pages: old.pages.map((page) => ({
...page,
items: page.items.filter((item) => item.id !== pageId),
})),
};
},
);
// Update old parent's hasChildren flag if it has no more children
if (oldParentId !== null) {
const oldParentCache = queryClient.getQueryData<
InfiniteData<IPagination<IPage>>
>(["sidebar-pages", { pageId: oldParentId, spaceId }]);
const remainingChildren =
oldParentCache?.pages.flatMap((p) => p.items).length ?? 0;
if (remainingChildren === 0) {
// Update hasChildren in all caches where old parent appears
const allSideBarMatches = queryClient.getQueriesData({
predicate: (query) =>
query.queryKey[0] === "root-sidebar-pages" ||
query.queryKey[0] === "sidebar-pages",
});
allSideBarMatches.forEach(([key]) => {
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(
key,
(old) => {
if (!old) return old;
return {
...old,
pages: old.pages.map((page) => ({
...page,
items: page.items.map((item) =>
item.id === oldParentId
? { ...item, hasChildren: false }
: item,
),
})),
};
},
);
});
}
}
// Add page to new parent's cache
const newQueryKey =
newParentId === null
? ["root-sidebar-pages", spaceId]
: ["sidebar-pages", { pageId: newParentId, spaceId }];
queryClient.setQueryData<InfiniteData<IPagination<Partial<IPage>>>>(
newQueryKey,
(old) => {
if (!old) return old;
// Check if page already exists in new location
const exists = old.pages.some((page) =>
page.items.some((item) => item.id === pageId),
);
if (exists) return old;
return {
...old,
pages: old.pages.map((page, index) => {
if (index === old.pages.length - 1) {
return {
...page,
items: [...page.items, pageData],
};
}
return page;
}),
};
},
);
// Update new parent's hasChildren flag
if (newParentId !== null) {
const allSideBarMatches = queryClient.getQueriesData({
predicate: (query) =>
query.queryKey[0] === "root-sidebar-pages" ||
query.queryKey[0] === "sidebar-pages",
});
allSideBarMatches.forEach(([key]) => {
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(key, (old) => {
if (!old) return old;
return {
...old,
pages: old.pages.map((page) => ({
...page,
items: page.items.map((item) =>
item.id === newParentId ? { ...item, hasChildren: true } : item,
),
})),
};
});
});
}
} }
export function invalidateOnDeletePage(pageId: string) { export function invalidateOnDeletePage(pageId: string) {
@@ -16,6 +16,7 @@ import {
useRemovePageMutation, useRemovePageMutation,
useMovePageMutation, useMovePageMutation,
useUpdatePageMutation, useUpdatePageMutation,
updateCacheOnMovePage,
} from "@/features/page/queries/page-query.ts"; } from "@/features/page/queries/page-query.ts";
import { generateJitteredKeyBetween } from "fractional-indexing-jittered"; import { generateJitteredKeyBetween } from "fractional-indexing-jittered";
import { SpaceTreeNode } from "@/features/page/tree/types.ts"; import { SpaceTreeNode } from "@/features/page/tree/types.ts";
@@ -175,9 +176,25 @@ export function useTreeMutation<T>(spaceId: string) {
parentPageId: args.parentId, parentPageId: args.parentId,
}; };
const draggedNode = args.dragNodes[0];
const nodeData = draggedNode.data as SpaceTreeNode;
const oldParentId = nodeData.parentPageId ?? null;
const pageData = {
id: nodeData.id,
slugId: nodeData.slugId,
title: nodeData.name,
icon: nodeData.icon,
position: newPosition,
spaceId: nodeData.spaceId,
parentPageId: args.parentId,
hasChildren: nodeData.hasChildren,
};
try { try {
await movePageMutation.mutateAsync(payload); await movePageMutation.mutateAsync(payload);
updateCacheOnMovePage(spaceId, draggedNodeId, oldParentId, args.parentId, pageData);
setTimeout(() => { setTimeout(() => {
emit({ emit({
operation: "moveTreeNode", operation: "moveTreeNode",
@@ -185,8 +202,10 @@ export function useTreeMutation<T>(spaceId: string) {
payload: { payload: {
id: draggedNodeId, id: draggedNodeId,
parentId: args.parentId, parentId: args.parentId,
oldParentId,
index: args.index, index: args.index,
position: newPosition, position: newPosition,
pageData,
}, },
}); });
}, 50); }, 50);
@@ -45,8 +45,10 @@ export type MoveTreeNodeEvent = {
payload: { payload: {
id: string; id: string;
parentId: string; parentId: string;
oldParentId: string | null;
index: number; index: number;
position: string; position: string;
pageData: Partial<IPage>;
}; };
}; };
@@ -8,7 +8,7 @@ import { IPagination } from "@/lib/types";
import { import {
invalidateOnCreatePage, invalidateOnCreatePage,
invalidateOnDeletePage, invalidateOnDeletePage,
invalidateOnMovePage, updateCacheOnMovePage,
invalidateOnUpdatePage, invalidateOnUpdatePage,
} from "../page/queries/page-query"; } from "../page/queries/page-query";
import { RQ_KEY } from "../comment/queries/comment-query"; import { RQ_KEY } from "../comment/queries/comment-query";
@@ -41,7 +41,13 @@ export const useQuerySubscription = () => {
invalidateOnCreatePage(data.payload.data); invalidateOnCreatePage(data.payload.data);
break; break;
case "moveTreeNode": case "moveTreeNode":
invalidateOnMovePage(); updateCacheOnMovePage(
data.spaceId,
data.payload.id,
data.payload.oldParentId,
data.payload.parentId,
data.payload.pageData,
);
break; break;
case "deleteTreeNode": case "deleteTreeNode":
invalidateOnDeletePage(data.payload.node.id); invalidateOnDeletePage(data.payload.node.id);