{"record":{"id":"b60b67117f5b74be","repo":"antiwork/gumroad","slug":"an-error-occurred-while-loading-more-items","errorCode":null,"errorMessage":"An error occurred while loading more items","messagePattern":"An error occurred while loading more items","errorType":"exception","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"app/javascript/data/wishlists.ts","lineNumber":50,"sourceCode":"    items: number;\n    page: number;\n    pages: number;\n    prev: number | null;\n    next: number | null;\n    last: number;\n  };\n};\n\nexport const fetchPaginatedWishlistItems = async ({\n  wishlist_id,\n  page,\n}: FetchPaginatedWishlistItemsArgs): Promise<PaginatedWishlistItems> => {\n  const response = await request({\n    method: \"GET\",\n    accept: \"json\",\n    url: Routes.wishlist_products_path(wishlist_id, { page }),\n  });\n  if (!response.ok) throw new ResponseError();\n  return typia.assert<PaginatedWishlistItems>(await response.json());\n};\n\nexport const addToWishlist = async ({\n  wishlistId,\n  productId,\n  optionId,\n  recurrence,\n  rent,\n  quantity,\n}: {\n  wishlistId: string;\n  productId: string;\n  optionId: string | null;\n  recurrence: string | null;\n  rent: boolean;\n  quantity: number | null;\n}) => {","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/data/wishlists.ts#L32-L68","documentation":"fetchPaginatedWishlistItems (app/javascript/data/wishlists.ts:41-52) GETs Routes.wishlist_products_path(wishlist_id, { page }) and throws generic ResponseError when response.ok is false; the infinite-scroll loader in components/Wishlist/index.tsx:198-214 catches it and toasts 'An error occurred while loading more items'. With 5xx/429/network handled upstream in request(), this is a 4xx: 404 (wishlist deleted or removed from public view, or page beyond range), 401 (session), 403 (private wishlist).","triggerScenarios":"Scrolling a wishlist page to trigger load-more after the wishlist was deleted or made private (404); requesting a page number that no longer exists because items were removed; viewer session expired mid-scroll (401); following a link to a wishlist whose owner blocked access (403).","commonSituations":"Wishlists edited/deleted while a follower has the page open; pagination state computed from a stale count; offline laptops resuming scroll; tests stubbing only page 1.","solutions":["Reload the wishlist page — if the wishlist is gone the server-rendered load will show it, ending the loop","Verify pagination.next is non-null before fetching (index.tsx:218 already gates the observer on it)","On failure, stop the infinite scroll rather than retrying the same page forever","Check the GET's status in the network tab to distinguish deleted (404) from expired session (401)"],"exampleFix":"// components/Wishlist/index.tsx — stop the observer after a failure instead of looping\nconst [loadFailed, setLoadFailed] = React.useState(false);\nif (e) { /* in catch */\n  assertResponseError(e);\n  showAlert(\"An error occurred while loading more items\", \"error\");\n  setLoadFailed(true);\n}\n// gate: if (e[0]?.isIntersecting && !loadingMore && !loadFailed && pagination.next) ...","handlingStrategy":"try-catch","validationCode":"const shouldLoadMore = pagination.next !== null && !loadingMore && !loadFailed;\nif (!shouldLoadMore) return; // index.tsx:218 already gates on pagination.next","typeGuard":"const isResponseError = (e: unknown): e is ResponseError => e instanceof ResponseError;","tryCatchPattern":"try {\n  const loaded = await fetchPaginatedWishlistItems({ wishlist_id: id, page: pagination.next });\n  setItems((prev) => uniqBy([...prev, ...loaded.items], \"id\"));\n} catch (e) {\n  assertResponseError(e);\n  setLoadFailed(true); // stop the IntersectionObserver from retrying forever\n  showAlert(\"An error occurred while loading more items\", \"error\");\n}","preventionTips":["Only fetch when pagination.next is non-null","Fail permanently for the session after one load-more failure instead of looping","Reload the page on persistent failure — the wishlist may have been deleted or privatized"],"tags":["wishlists","http","pagination","infinite-scroll","fetch"],"backgroundTag":"http-404-not-found","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}