♻️(frontend) improve pagination UX and simplify mutation hooks

Use placeholderData in useInfiniteChildren and
useInfiniteItems to prevent layout flicker on
re-fetch. Remove addItemToTop from
useOptimisticPagination. Simplify useMutations
by removing unused logic.
This commit is contained in:
Nathan Panchout
2026-03-31 10:36:51 +02:00
parent b6ac733c72
commit ee07fb2a38
4 changed files with 7 additions and 95 deletions
@@ -31,5 +31,6 @@ export const useInfiniteChildren = (
},
initialPageParam: 1,
enabled: enabled && itemId !== null,
placeholderData: (previousData) => previousData,
});
};
@@ -32,6 +32,7 @@ const createInfiniteItemsHook = (
},
initialPageParam: 1,
enabled: enabled,
placeholderData: (previousData) => previousData,
});
};
};
@@ -5,10 +5,7 @@ import {
useGlobalExplorer,
generateTreeId,
} from "../components/GlobalExplorerContext";
import {
useAddItemToPaginatedList,
useRemoveItemsFromPaginatedList,
} from "./useOptimisticPagination";
import { useRemoveItemsFromPaginatedList } from "./useOptimisticPagination";
import { useTreeContext } from "@gouvfr-lasuite/ui-kit";
import {
useRefreshQueryCacheAfterMutation,
@@ -122,17 +119,14 @@ export const useMutationRenameItem = () => {
export const useMutationCreateFolder = () => {
const driver = getDriver();
const addItemToTopOfPaginatedList = useAddItemToPaginatedList();
const refresh = useRefreshQueryCacheAfterMutation();
return useMutation({
mutationFn: (...payload: Parameters<typeof driver.createFolder>) => {
return driver.createFolder(...payload);
},
onSuccess: (data, variables) => {
const queryKey = variables.parentId
? ["items", variables.parentId, "children"]
: ["items", "infinite", JSON.stringify({ is_creator_me: true })];
addItemToTopOfPaginatedList(queryKey, data);
onSuccess: (_, variables) => {
refresh(variables.parentId);
},
});
};
@@ -258,10 +252,7 @@ export const useMutationDeleteFavoriteItem = () => {
true,
);
treeContext?.treeData.deleteNode(rootFavoriteTreeId);
removeItems(
["items", "infinite", JSON.stringify({ is_favorite: true })],
[itemId],
);
removeItems(["items", "infinite"], [itemId]);
refreshItemCache(itemId, { is_favorite: false });
refreshFavoriteCache(itemId, false);
},
@@ -2,60 +2,6 @@ import { Item } from "@/features/drivers/types";
import { PaginatedChildrenResult } from "@gouvfr-lasuite/ui-kit";
import { QueryClient, QueryKey, useQueryClient } from "@tanstack/react-query";
/**
* Adds an item to the top of the first page of a paginated infinite query list.
* This function finds all queries matching the queryKey pattern and updates them
* by prepending the new item to the first page.
*
* @param queryClient - The react-query QueryClient instance
* @param queryKey - The query key pattern to match (can be partial)
* @param newItem - The item to add at the top of the list
*/
export const addItemToTopOfPaginatedList = (
queryClient: QueryClient,
queryKey: QueryKey,
newItem: Item
): void => {
// Get all queries matching the queryKey pattern
const queriesData = queryClient.getQueriesData({
queryKey,
});
queriesData.forEach((query) => {
const key = query[0];
const data = query[1] as
| { pages: PaginatedChildrenResult<Item>[] }
| undefined;
if (!data || !data.pages || data.pages.length === 0) {
return;
}
// Deep clone to avoid mutating the original data
const updatedData: { pages: PaginatedChildrenResult<Item>[] } = JSON.parse(
JSON.stringify(data)
);
// Add the new item to the top of the first page
if (updatedData.pages[0]) {
// Check if item already exists to avoid duplicates
const itemExists = updatedData.pages.some((page) =>
page.children?.some((child) => child.id === newItem.id)
);
if (!itemExists) {
updatedData.pages[0].children = [
newItem,
...(updatedData.pages[0].children || []),
];
}
}
// Update the query data
queryClient.setQueryData(key, updatedData);
});
};
/**
* Removes items from a paginated infinite query list.
* This function finds all queries matching the queryKey pattern and updates them
@@ -102,33 +48,6 @@ export const removeItemsFromPaginatedList = (
});
};
/**
* Hook that returns a function to add an item to the top of a paginated list.
* This is a convenience hook that provides access to the queryClient.
*
* @example
* ```tsx
* const addItemToTop = useAddItemToPaginatedList();
*
* // In a mutation's onSuccess callback:
* onSuccess: (newItem) => {
* addItemToTop(
* ["items", parentId, "children", "infinite"],
* newItem
* );
* }
* ```
*
* @returns A function that takes a queryKey and an item, and adds the item to the top of the list
*/
export const useAddItemToPaginatedList = () => {
const queryClient = useQueryClient();
return (queryKey: QueryKey, newItem: Item) => {
addItemToTopOfPaginatedList(queryClient, queryKey, newItem);
};
};
/**
* Hook that returns a function to remove items from a paginated list.
* This is a convenience hook that provides access to the queryClient.