Skip to content

Matikun/infinite-scroll-flaky-api

Repository files navigation

HomeVision — Infinite House Gallery

A React + TypeScript app that loads house listings from the HomeVision staging API in an infinite-scrolling, virtualized grid. The implementation focuses on production-style concerns that matter for a long, paginated feed: keeping scrolling smooth as more pages load, handling a flaky API without throwing away already-loaded content, and degrading gracefully when listing images fail.

Tech stack

  • React 19 with TypeScript
  • Vite for dev server and production bundle
  • TanStack Query (useInfiniteQuery) for pagination, caching, and retry
  • TanStack Virtual for window-based row virtualization
  • Tailwind CSS + shadcn/ui primitives for layout and components
  • Vitest, Testing Library, and MSW for unit and integration tests

Prerequisites

  • Node.js (see .node-version / .nvmrc)
  • pnpm (see package.json#packageManager)

Getting started

cd homevision
pnpm install --frozen-lockfile

Notes:

  • If you see Ignored build scripts: msw@... during pnpm install, this is expected in some pnpm configurations that block dependency install scripts by default. MSW is used in Node-based tests (msw/node) and does not rely on a browser Service Worker in this project.

Development

pnpm dev

Opens the Vite dev server (default: http://localhost:5173).

Tests

pnpm test              # watch mode
pnpm test -- --run     # single run (CI-style)

E2E (Playwright)

pnpm e2e

Notes:

  • If Playwright has not downloaded Chromium yet, run pnpm exec playwright install chromium once and then re-run pnpm e2e.

Lint

pnpm lint

Production build

pnpm build

Preview production build locally

pnpm preview

Full verification (lint + tests + build)

pnpm validate

Architecture (brief)

  • src/api/client.tsfetchHouses; normalizes the API shape (photoURLphotoUrl) and throws a structured ApiError on HTTP failures.
  • src/hooks/use-houses.ts — Wraps useInfiniteQuery: flattens pages, deduplicates by id, configures retries for transient failures, and maps errors to a plain string (errorMessage) so UI components never import ApiError.
  • src/hooks/use-grid-layout.ts — Single ResizeObserver on the grid container; derives columnCount from actual container width and scrollMargin from offsetTop. Replaces hardcoded JS breakpoints.
  • src/components/house-grid.tsx — Responsive virtualized rows using CSS Grid auto-fill; prefetches the next page near the end of loaded rows; composes cards, skeletons, and InlineError.
  • src/components/house-card.tsx, house-image.tsx — Presentation; images use lazy loading and an onError fallback so broken URLs do not break the grid.

main.tsx wraps the app in QueryClientProvider with shared defaults for retries, exponential backoff, and a short staleTime.

Design decisions

Error handling

Errors are normalized at the API layer (ApiError in client.ts) and mapped to a user-facing string inside useHouses, which exposes errorMessage: string | null. UI components never import ApiError — they render plain strings.

  • Initial load failure: A full-screen InlineError with a "Try again" button.
  • Later page failure: Already-loaded cards stay on screen; an inline error banner appears below. handleRetry calls fetchNextPage when houses exist, or refetch for an empty list — this invariant ensures the right recovery path is taken.
  • Retry strategy (Query layer): Up to 3 retries with exponential backoff for transient 5xx / network errors. 4xx errors are not retried. The API client stays thin and throws normalized errors; retry logic lives in React Query to avoid double-retries.

Data integrity

useHouses flattens the loaded pages and deduplicates listings by id before rendering. This is a small defensive step, but it helps a production feed stay stable if paginated responses overlap or the upstream API returns repeated records.

Virtualization

This app uses infinite scroll, so the number of loaded listings can keep growing during a session. Without virtualization, every fetched card would remain mounted in the DOM, which increases layout work, memory usage, and the cost of re-rendering as the list grows. Virtualization keeps the scroll experience stable by only mounting the rows near the viewport while preserving the same infinite-feed behavior.

@tanstack/react-virtual virtualizes rows of cards — the TanStack-recommended pattern for responsive grids. Column count is not hardcoded via JS breakpoints; useGridLayout uses a callback ref so a ResizeObserver runs as soon as the width-constrained container mounts and derives columns from measured container width. This keeps the virtualizer's row math in sync with CSS Grid auto-fill without duplicating Tailwind breakpoints in JS.

Each virtual row renders its items inside a CSS Grid container (grid-cols-[repeat(auto-fill,minmax(min(100%,280px),1fr))]), so the browser controls column sizing and JS only supplies the numeric column count needed for row slicing. useWindowVirtualizer also receives the measured scrollMargin, which keeps the visible-row calculation aligned with the document scroll position instead of assuming the grid starts at the top of the page.

This adds some implementation and testing complexity, but it is a deliberate trade-off for an infinite list: the feed should remain responsive even after many pages have been loaded.

Images

Broken photoUrl values are handled in HouseImage (fallback asset), separate from API retries — network/API errors and image load errors are different failure modes.

Test limitations

Unit tests mock @tanstack/react-virtual entirely so every virtual row renders unconditionally. This validates data-flow, error UI, image fallback behavior, and retry logic without requiring a real browser viewport. Scroll integration, estimateSize, scrollMargin, and prefetch thresholds are not covered by unit tests — an optional Playwright or E2E spec is recommended for regression safety on those paths.

Accessibility

The shell uses semantic regions (header, main). Cards use article where appropriate. Loading and error announcements use live regions; retry controls are keyboard-focusable. Image alt text prefers address or homeowner when available.

Manual smoke test (submitter / reviewer)

After pnpm build && pnpm preview:

  1. Scroll the grid; additional pages should load.
  2. If the API misbehaves, errors should appear with recovery; already loaded items should remain visible after a failed "next page."
  3. Broken listing images should show the fallback graphic, not a broken image icon.
  4. Keep scrolling through multiple pages; the feed should remain responsive instead of feeling heavier as more results accumulate.

Verification

Run the combined check anytime:

pnpm validate

This runs lint + vitest run + build in sequence.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages