Skip to content
Alvaro Sarria
Menu
← All work

HomeBrew Apartments

Client-only browser for 100 short-term rentals across Madrid, Berlin and Paris, with debounced search, a saved list and host publishing.

Problem

Apartment listing sites are often cluttered with ads and slow search. I wanted a fast, focused browsing experience for a single listing source.

Approach

A single-page React app with no server, no database and no API: the catalogue of 100 listings ships with the app as a JSON file, and anything you save or publish is stored in your browser’s localStorage. That constraint shapes most of the design. You search by city or country, open a listing for its photos, facts, price breakdown and host, save the ones you like, and can publish a listing of your own into the same grid.

Architecture

  • State: App.jsx is the single source of truth. There is no context and no store — state is passed down as props, deliberately, for an app this size
  • The catalogue loads through a useCatalogue hook that dynamically imports the listing JSON, keeping it out of the initial chunk
  • Search results are derived, never stored: they are recomputed from the full listing set plus a debounced query on every render
  • Persistence: saved listings are ids in localStorage, with the listing objects resolved from the full catalogue; host-created listings are prepended to it
  • Styling: CSS Modules per component over a tokens.css design system — no raw hex values or magic pixel numbers outside it

Key decisions

  • Deriving results rather than storing them fixed a real bug: an earlier version kept results in their own state and they drifted from the search input. For the same reason, lookups go against the full catalogue and never against the filtered view, which resolves to undefined for anything the current search excludes
  • ApartmentCard is memoized and takes a plain boolean rather than an isFavorite predicate, whose identity changes on every toggle and would re-render all 100 cards
  • The catalogue is generated at build time from the original data dump, dropping the roughly 60% of fields nothing renders — the generated file is committed so a fresh clone runs immediately
  • The display serif carries the wordmark and every number — prices, ratings, city counts — while UI text uses the system stack

Results

A responsive browser over 100 listings with case-insensitive, debounced city and country search, a live city ledger above the grid that doubles as filter chips and the reset control, a saved list and published listings that both survive a reload, and light and dark themes following the system preference until you choose one. A Vitest suite covers the defects that actually occurred, and lint runs at zero tolerated warnings.

What I’d do differently

I’d paginate or virtualize the grid — client-side filtering over a much larger dataset than this one would need it. The bigger limitation is localStorage: saved and published listings never leave the browser they were created in, so a real backend is the next step rather than an optimization.