Executive Summary
CarHub is a React and TypeScript car catalog built with Vite, browsing a real public vehicle dataset through filters and pagination that live entirely in the URL — refresh the page or share the link, and the exact same results come back.
The Problem Space
Before writing any UI, I identified what actually breaks in listing platforms once they leave the tutorial stage:
- Filter State That Doesn't Survive: Component-state filters vanish on refresh and can't be shared as a link.
- Mock Data Hides Real Problems: A hardcoded array never forces you to handle a slow, occasionally messy third-party API honestly.
- Client-Side Pagination Doesn't Scale: Fetching everything up front falls apart against a dataset with thousands of vehicles.
System Architecture
CarHub is a React 19 and TypeScript front end built with Vite. Filter and pagination state live entirely in the URL via React Router's useSearchParams — there's no separate component state duplicating what the URL already holds, so back/forward navigation and shared links just work.
- URL as the Source of Truth: Make, model, year, and page all read from and write to the query string.
- Server-Side Pagination: The results grid re-fetches from the API whenever the URL params change, computing limit/offset instead of slicing a full dataset client-side.
- Real Public Data: Listings come from OpenDataSoft's public vehicle dataset, not a mocked catalog.
Technical Implementation
The project is built on React and TypeScript with Vite, using React Router for URL-synced filter state and the OpenDataSoft public dataset API for real vehicle data. Here's how the core listing fetch works:
export const fetchCars = async (
make: string,
model: string,
year: string,
page: string,
limit: number,
): Promise<CarResponse> => {
const base =
"https://public.opendatasoft.com/api/explore/v2.1/catalog" +
"/datasets/all-vehicles-model/records";
let url = `${base}?limit=${limit}`;
if (make) url += `&refine=make:"${make}"`;
if (model) url += `&refine=model:"${model}"`;
if (year) url += `&refine=year:"${year}"`;
const offset = (Number(page) - 1) * limit;
url += `&offset=${offset}`;
const res = await fetch(url);
return await res.json();
};Key Features
- Marka, Model & Year Filters: A searchable make select, free-text model input, and year filter, all synced to the URL on submit.
- Real Vehicle Data: Every result comes from a live public dataset, not a mocked catalog.
- Shareable Pagination: Page state lives in the URL and scrolls back to the top of the results on change.
- Explicit Loading, Empty & Error States: The grid never silently hangs — it shows a loader, a real error message, or an honest empty state.
Impact & Results
- Filter and pagination state is shareable and survives a refresh by design, not bolted on afterward.
- Working against a real, occasionally messy public dataset taught me to treat loading and error states as first-class, not edge cases.
- Reinforced URL-as-state as a pattern I now reach for by default in listing UIs.
