mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-03 21:04:59 +00:00
fix: refetch favorites when toggled before the list loads, lint fixes
An optimistic favorite written over an unpopulated cache seeded the list with only the toggled item, and cancelQueries killed the initial fetch that would have corrected it, hiding existing favorites until reload. The optimistic write now only applies over known data; otherwise onSettled invalidates so the authoritative list is refetched. Also unnest the version date-label ternary and drop an unused form watch flagged by CI.
This commit is contained in:
parent
2a3e14c850
commit
05ac524fd9
4 changed files with 53 additions and 13 deletions
|
|
@ -53,11 +53,12 @@ export default function VersionItem({
|
|||
const date = getTimestampDate(version);
|
||||
const hasUpdatedAt = version.updatedAt != null;
|
||||
const hasCreatedAt = version.createdAt != null;
|
||||
const relativeLabel = date
|
||||
? formatDistanceToNow(date, { addSuffix: true })
|
||||
: hasUpdatedAt || hasCreatedAt
|
||||
? localize('com_ui_agent_version_unknown_date')
|
||||
: localize('com_ui_agent_version_no_date');
|
||||
const fallbackDateLabel = localize(
|
||||
hasUpdatedAt || hasCreatedAt
|
||||
? 'com_ui_agent_version_unknown_date'
|
||||
: 'com_ui_agent_version_no_date',
|
||||
);
|
||||
const relativeLabel = date ? formatDistanceToNow(date, { addSuffix: true }) : fallbackDateLabel;
|
||||
const absoluteLabel = date ? date.toLocaleString() : relativeLabel;
|
||||
|
||||
const toolsCount = countItems(version.tools);
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export default function ActionsPanel({
|
|||
},
|
||||
});
|
||||
|
||||
const { reset, watch } = methods;
|
||||
const { reset } = methods;
|
||||
|
||||
useEffect(() => {
|
||||
if (action?.metadata?.auth) {
|
||||
|
|
|
|||
|
|
@ -62,13 +62,20 @@ export const useGetToolFavoritesQuery = (
|
|||
export const useAddToolFavoriteMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation((favorite: TToolFavorite) => dataService.addToolFavorite(favorite), {
|
||||
/** Optimistic writes only apply over known server data. Before the list
|
||||
* query has populated, seeding the cache from `[]` would make the toggled
|
||||
* item look like the user's only favorite (and `cancelQueries` kills the
|
||||
* initial fetch that would correct it) — so skip the write and let
|
||||
* `onSettled` refetch the authoritative list instead. */
|
||||
onMutate: async (favorite) => {
|
||||
await queryClient.cancelQueries([QueryKeys.toolFavorites]);
|
||||
const previous = queryClient.getQueryData<TToolFavorite[]>([QueryKeys.toolFavorites]);
|
||||
queryClient.setQueryData<TToolFavorite[]>([QueryKeys.toolFavorites], (current) => {
|
||||
const list = current ?? [];
|
||||
return list.some((f) => sameFavorite(f, favorite)) ? list : [...list, favorite];
|
||||
});
|
||||
if (previous !== undefined) {
|
||||
queryClient.setQueryData<TToolFavorite[]>(
|
||||
[QueryKeys.toolFavorites],
|
||||
previous.some((f) => sameFavorite(f, favorite)) ? previous : [...previous, favorite],
|
||||
);
|
||||
}
|
||||
return { previous };
|
||||
},
|
||||
onError: (_err, _favorite, context) => {
|
||||
|
|
@ -76,6 +83,11 @@ export const useAddToolFavoriteMutation = () => {
|
|||
queryClient.setQueryData([QueryKeys.toolFavorites], context.previous);
|
||||
}
|
||||
},
|
||||
onSettled: (_data, _err, _favorite, context) => {
|
||||
if (context?.previous === undefined) {
|
||||
queryClient.invalidateQueries([QueryKeys.toolFavorites]);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -85,9 +97,12 @@ export const useRemoveToolFavoriteMutation = () => {
|
|||
onMutate: async (favorite) => {
|
||||
await queryClient.cancelQueries([QueryKeys.toolFavorites]);
|
||||
const previous = queryClient.getQueryData<TToolFavorite[]>([QueryKeys.toolFavorites]);
|
||||
queryClient.setQueryData<TToolFavorite[]>([QueryKeys.toolFavorites], (current) =>
|
||||
(current ?? []).filter((f) => !sameFavorite(f, favorite)),
|
||||
);
|
||||
if (previous !== undefined) {
|
||||
queryClient.setQueryData<TToolFavorite[]>(
|
||||
[QueryKeys.toolFavorites],
|
||||
previous.filter((f) => !sameFavorite(f, favorite)),
|
||||
);
|
||||
}
|
||||
return { previous };
|
||||
},
|
||||
onError: (_err, _favorite, context) => {
|
||||
|
|
@ -95,5 +110,10 @@ export const useRemoveToolFavoriteMutation = () => {
|
|||
queryClient.setQueryData([QueryKeys.toolFavorites], context.previous);
|
||||
}
|
||||
},
|
||||
onSettled: (_data, _err, _favorite, context) => {
|
||||
if (context?.previous === undefined) {
|
||||
queryClient.invalidateQueries([QueryKeys.toolFavorites]);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -98,6 +98,25 @@ describe('useToolFavorites', () => {
|
|||
expect(result.current.isFavorite({ kind: 'action', id: 'a1' })).toBe(false);
|
||||
});
|
||||
|
||||
test('starring before the list loads refetches instead of hiding existing favorites', async () => {
|
||||
mockGetToolFavorites
|
||||
.mockImplementationOnce(() => new Promise<TToolFavorite[]>(() => undefined))
|
||||
.mockResolvedValueOnce([...seeded, { itemType: 'skill', itemId: 's1' }]);
|
||||
|
||||
const { result } = renderHook(() => useToolFavorites(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.toggle({ kind: 'skill', id: 's1' });
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current.favoriteKeys).toEqual(
|
||||
new Set(['tool:dalle', 'mcp:everything', 'skill:s1']),
|
||||
),
|
||||
);
|
||||
expect(mockGetToolFavorites).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('shows the item-worded cap toast and rolls back on rejection', async () => {
|
||||
mockAddToolFavorite.mockRejectedValue({
|
||||
response: { data: { code: 'MAX_FAVORITES_EXCEEDED', limit: 100 } },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue