{"record":{"id":"5126a603ddc7639d","repo":"antiwork/gumroad","slug":"something-went-wrong-5126a6","errorCode":null,"errorMessage":"Something went wrong.","messagePattern":"Something went wrong\\.","errorType":"exception","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"app/javascript/data/wishlists.ts","lineNumber":152,"sourceCode":"export const followWishlist = async ({ wishlistId }: { wishlistId: string }) => {\n  const response = await request({\n    method: \"POST\",\n    url: Routes.wishlist_followers_path(wishlistId),\n    accept: \"json\",\n  });\n  if (!response.ok) {\n    const data = typia.assert<{ error: string }>(await response.json());\n    throw new ResponseError(data.error);\n  }\n};\n\nexport const unfollowWishlist = async ({ wishlistId }: { wishlistId: string }) => {\n  const response = await request({\n    method: \"DELETE\",\n    url: Routes.wishlist_followers_path(wishlistId),\n    accept: \"json\",\n  });\n  if (!response.ok) throw new ResponseError();\n};\n\nexport const fetchWishlists = async (ids: string[]) => {\n  const response = await request({\n    method: \"GET\",\n    url: Routes.wishlists_path({ ids }),\n    accept: \"json\",\n  });\n  if (!response.ok) throw new ResponseError();\n  return typia.assert<CardWishlist[]>(await response.json());\n};\n","sourceCodeStart":134,"sourceCodeEnd":164,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/data/wishlists.ts#L134-L164","documentation":"unfollowWishlist (app/javascript/data/wishlists.ts:146-153) DELETEs Routes.wishlist_followers_path(wishlistId) and throws generic ResponseError ('Something went wrong.') on !response.ok — unlike followWishlist it does not parse the error body. With 5xx/429/network handled inside request(), this is a 4xx: 404 (not following anymore — follower record gone, or wishlist deleted), 401 (expired session), 403. Called from FollowButton.tsx:38 (toggle) and the Following page (pages/Wishlists/Following/Index.tsx:43).","triggerScenarios":"Clicking Unfollow when the follower record was already removed (double click, stale 'following' badge, unfollowed in another tab); unfollowing a wishlist deleted by its owner (404); session expiring before the click (401).","commonSituations":"Following-list pages open across tabs; follow toggles racing on slow networks; state desync where the button shows Following but the server already removed the record; tests unfollowing twice.","solutions":["Refresh so the following state comes from the server, then only show Unfollow when truly following","Treat 'already unfollowed' (404) as success and update local state","Disable the toggle while the unfollow is in flight","Check the DELETE's status in the network tab to distinguish 404/401/403"],"exampleFix":"// components/Wishlist/FollowButton.tsx — converge on failure instead of only toasting\ntry {\n  await unfollowWishlist({ wishlistId });\n} catch (e) {\n  assertResponseError(e);\n  setIsFollowing(false); // record may already be gone; align with server\n  showAlert(e.message, \"error\");\n}","handlingStrategy":"try-catch","validationCode":"const canUnfollow = (isFollowing: boolean) => isFollowing;\nif (!canUnfollow(isFollowing)) return; // no follower record to DELETE — the call would 404","typeGuard":"const isResponseError = (e: unknown): e is ResponseError => e instanceof ResponseError;","tryCatchPattern":"try {\n  await unfollowWishlist({ wishlistId });\n} catch (e) {\n  assertResponseError(e);\n  setIsFollowing(false); // record may already be gone; align local state with server\n  showAlert(e.message, \"error\");\n}","preventionTips":["Only show Unfollow when the server-rendered state says following","Treat 404-already-unfollowed as success and update local state","Disable the toggle while the DELETE is in flight"],"tags":["wishlists","http","delete","follow-system","state-desync"],"backgroundTag":"http-404-not-found","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}