mirror of
https://github.com/linkwarden/linkwarden.git
synced 2026-03-03 03:57:01 +00:00
feat: enhance deleteLink functionality with optimistic rendering and improved error handling
This commit is contained in:
@@ -121,7 +121,7 @@ const RootComponent = ({
|
||||
}) => {
|
||||
const { colorScheme } = useColorScheme();
|
||||
const updateLink = useUpdateLink(auth);
|
||||
const deleteLink = useDeleteLink(auth);
|
||||
const deleteLink = useDeleteLink({ auth, Alert });
|
||||
|
||||
const { tmp } = useTmpStore();
|
||||
|
||||
@@ -282,18 +282,15 @@ const RootComponent = ({
|
||||
{
|
||||
text: "Delete",
|
||||
style: "destructive",
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
deleteLink.mutate(
|
||||
tmp.link?.id as number,
|
||||
{
|
||||
onSuccess: async () => {
|
||||
await deleteLinkCache(
|
||||
tmp.link?.id as number
|
||||
);
|
||||
},
|
||||
}
|
||||
tmp.link?.id as number
|
||||
);
|
||||
// go back
|
||||
|
||||
await deleteLinkCache(
|
||||
tmp.link?.id as number
|
||||
);
|
||||
|
||||
router.back();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ const LinkListing = ({ link, dashboard }: Props) => {
|
||||
const { colorScheme } = useColorScheme();
|
||||
const { data } = useDataStore();
|
||||
|
||||
const deleteLink = useDeleteLink(auth);
|
||||
const deleteLink = useDeleteLink({ auth, Alert });
|
||||
|
||||
const [url, setUrl] = useState("");
|
||||
|
||||
@@ -319,12 +319,10 @@ const LinkListing = ({ link, dashboard }: Props) => {
|
||||
{
|
||||
text: "Delete",
|
||||
style: "destructive",
|
||||
onPress: () => {
|
||||
deleteLink.mutate(link.id as number, {
|
||||
onSuccess: async () => {
|
||||
await deleteLinkCache(link.id as number);
|
||||
},
|
||||
});
|
||||
onPress: async () => {
|
||||
deleteLink.mutate(link.id as number);
|
||||
|
||||
await deleteLinkCache(link.id as number);
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function LinkActions({
|
||||
const [refreshPreservationsModal, setRefreshPreservationsModal] =
|
||||
useState(false);
|
||||
|
||||
const deleteLink = useDeleteLink();
|
||||
const deleteLink = useDeleteLink({ toast, t });
|
||||
|
||||
const updateArchive = async () => {
|
||||
const load = toast.loading(t("sending_request"));
|
||||
@@ -131,13 +131,7 @@ export default function LinkActions({
|
||||
onClick={async (e) => {
|
||||
if (e.shiftKey) {
|
||||
const load = toast.loading(t("deleting"));
|
||||
await deleteLink.mutateAsync(link.id as number, {
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(load);
|
||||
if (error) toast.error(error.message);
|
||||
else toast.success(t("deleted"));
|
||||
},
|
||||
});
|
||||
await deleteLink.mutateAsync(link.id as number);
|
||||
} else {
|
||||
setDeleteLinkModal(true);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) {
|
||||
const [link, setLink] =
|
||||
useState<LinkIncludingShortenedCollectionAndTags>(activeLink);
|
||||
|
||||
const deleteLink = useDeleteLink();
|
||||
const deleteLink = useDeleteLink({ toast, t });
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -26,26 +26,15 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) {
|
||||
}, []);
|
||||
|
||||
const submit = async () => {
|
||||
const load = toast.loading(t("deleting"));
|
||||
deleteLink.mutateAsync(link.id as number);
|
||||
|
||||
await deleteLink.mutateAsync(link.id as number, {
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(load);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
if (
|
||||
router.pathname.startsWith("/links/[id]") ||
|
||||
router.pathname.startsWith("/preserved/[id]")
|
||||
) {
|
||||
router.push("/dashboard");
|
||||
}
|
||||
toast.success(t("deleted"));
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
});
|
||||
if (
|
||||
router.pathname.startsWith("/links/[id]") ||
|
||||
router.pathname.startsWith("/preserved/[id]")
|
||||
) {
|
||||
router.push("/dashboard");
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function LinkModal({
|
||||
|
||||
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
||||
|
||||
const deleteLink = useDeleteLink();
|
||||
const deleteLink = useDeleteLink({ toast, t });
|
||||
|
||||
const [mode, setMode] = useState<"view" | "edit">(activeMode || "view");
|
||||
|
||||
@@ -51,13 +51,8 @@ export default function LinkModal({
|
||||
setTimeout(() => (document.body.style.pointerEvents = ""), 0);
|
||||
|
||||
if (e.shiftKey && link.id) {
|
||||
const loading = toast.loading(t("deleting"));
|
||||
await deleteLink.mutateAsync(link.id, {
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(loading);
|
||||
error ? toast.error(error.message) : toast.success(t("deleted"));
|
||||
},
|
||||
});
|
||||
deleteLink.mutateAsync(link.id);
|
||||
|
||||
onClose();
|
||||
} else {
|
||||
onDelete();
|
||||
|
||||
@@ -193,6 +193,48 @@ const upsertLinkInDashboardData = (
|
||||
};
|
||||
};
|
||||
|
||||
const removeLinkFromInfiniteData = (oldData: any, linkId: number) => {
|
||||
if (!oldData?.pages?.length) return oldData;
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
pages: oldData.pages.map((page: any) => ({
|
||||
...page,
|
||||
links: (page.links ?? []).filter((item: any) => item.id !== linkId),
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
const removeLinkFromDashboardData = (oldData: any, linkId: number) => {
|
||||
if (!oldData) return oldData;
|
||||
|
||||
const removedLink = (oldData.links ?? []).find(
|
||||
(item: any) => item.id === linkId
|
||||
);
|
||||
const numberOfPinnedLinks = removedLink?.pinnedBy?.length
|
||||
? Math.max(0, (oldData.numberOfPinnedLinks ?? 0) - 1)
|
||||
: oldData.numberOfPinnedLinks;
|
||||
|
||||
const hasCollectionLinks = oldData.collectionLinks != null;
|
||||
const collectionLinks = hasCollectionLinks
|
||||
? { ...oldData.collectionLinks }
|
||||
: oldData.collectionLinks;
|
||||
if (hasCollectionLinks) {
|
||||
for (const [collectionId, links] of Object.entries(collectionLinks)) {
|
||||
collectionLinks[Number(collectionId)] = (links as any[]).filter(
|
||||
(item: any) => item.id !== linkId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
links: (oldData.links ?? []).filter((item: any) => item.id !== linkId),
|
||||
collectionLinks,
|
||||
numberOfPinnedLinks,
|
||||
};
|
||||
};
|
||||
|
||||
const useAddLink = ({
|
||||
auth,
|
||||
Alert,
|
||||
@@ -393,7 +435,17 @@ const useUpdateLink = (auth?: MobileAuth) => {
|
||||
});
|
||||
};
|
||||
|
||||
const useDeleteLink = (auth?: MobileAuth) => {
|
||||
const useDeleteLink = ({
|
||||
auth,
|
||||
Alert,
|
||||
toast,
|
||||
t,
|
||||
}: {
|
||||
auth?: MobileAuth;
|
||||
Alert?: typeof Alert_;
|
||||
toast?: typeof toaster;
|
||||
t?: TFunction;
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
@@ -416,6 +468,57 @@ const useDeleteLink = (auth?: MobileAuth) => {
|
||||
|
||||
return data.response;
|
||||
},
|
||||
onMutate: async (id) => {
|
||||
await queryClient.cancelQueries({ queryKey: ["links"] });
|
||||
await queryClient.cancelQueries({ queryKey: ["dashboardData"] });
|
||||
await queryClient.cancelQueries({ queryKey: ["publicLinks"] });
|
||||
|
||||
const previousLinks = queryClient.getQueriesData({
|
||||
queryKey: ["links"],
|
||||
});
|
||||
const previousPublicLinks = queryClient.getQueriesData({
|
||||
queryKey: ["publicLinks"],
|
||||
});
|
||||
const previousDashboard = queryClient.getQueryData(["dashboardData"]);
|
||||
|
||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) =>
|
||||
removeLinkFromInfiniteData(oldData, id)
|
||||
);
|
||||
|
||||
queryClient.setQueriesData(
|
||||
{ queryKey: ["publicLinks"] },
|
||||
(oldData: any) => removeLinkFromInfiniteData(oldData, id)
|
||||
);
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], (oldData: any) =>
|
||||
removeLinkFromDashboardData(oldData, id)
|
||||
);
|
||||
|
||||
return {
|
||||
previousLinks,
|
||||
previousPublicLinks,
|
||||
previousDashboard,
|
||||
};
|
||||
},
|
||||
onError: (error, _variables, context) => {
|
||||
if (toast && t) toast.error(t(error.message));
|
||||
else if (Alert)
|
||||
Alert.alert("Error", "There was an error adding the link.");
|
||||
|
||||
if (!context) return;
|
||||
|
||||
context.previousLinks?.forEach(([queryKey, data]: [unknown, unknown]) => {
|
||||
queryClient.setQueryData(queryKey as QueryKey, data);
|
||||
});
|
||||
|
||||
context.previousPublicLinks?.forEach(
|
||||
([queryKey, data]: [unknown, unknown]) => {
|
||||
queryClient.setQueryData(queryKey as QueryKey, data);
|
||||
}
|
||||
);
|
||||
|
||||
queryClient.setQueryData(["dashboardData"], context.previousDashboard);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => {
|
||||
if (!oldData?.pages?.[0]) return undefined;
|
||||
|
||||
Reference in New Issue
Block a user