⚛️ intermediate 20 minutes
React Integration Guide
Build a property search interface in React with custom hooks, state management, and reusable components powered by BayutAPI.
What you'll build: A React property search UI with filters and listing cards
Prerequisites: React 18+ · Node.js 18+ · RapidAPI account (free) · Basic React knowledge
Prerequisites
- React 18+ project (Create React App, Vite, or similar)
- A RapidAPI account with a BayutAPI subscription (get your free API key)
- Familiarity with React hooks and async patterns
Installation
Create a new React project if you don’t have one:
npm create vite@latest my-property-app -- --template react
cd my-property-app
npm install
No additional API client libraries are needed — the native fetch API works perfectly.
Authentication Setup
Create a configuration file for your API credentials. Never expose your API key in client-side code in production — use a backend proxy instead:
// src/config/api.js
export const API_CONFIG = {
baseUrl: "https://bayut14.p.rapidapi.com",
headers: {
"x-rapidapi-key": import.meta.env.VITE_RAPIDAPI_KEY,
"x-rapidapi-host": "bayut14.p.rapidapi.com",
},
};
Add your key to .env:
VITE_RAPIDAPI_KEY=your_api_key_here
Basic Example: Property Search Hook
Create a custom hook that handles property searching, loading states, and pagination:
// src/hooks/usePropertySearch.js
import { useState, useCallback } from "react";
import { API_CONFIG } from "../config/api";
export function usePropertySearch() {
const [properties, setProperties] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [pagination, setPagination] = useState({ page: 1, totalPages: 0, total: 0 });
const searchProperties = useCallback(async (filters = {}) => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams({
purpose: filters.purpose || "for-sale",
page: (filters.page || 1).toString(),
...(filters.locationId && { location_ids: filters.locationId }),
...(filters.minPrice && { price_min: filters.minPrice }),
...(filters.maxPrice && { price_max: filters.maxPrice }),
...(filters.bedrooms && { bedrooms_min: filters.bedrooms, bedrooms_max: filters.bedrooms }),
});
const response = await fetch(
`${API_CONFIG.baseUrl}/search-property?${params}`,
{ headers: API_CONFIG.headers }
);
if (!response.ok) throw new Error(`API error: ${response.status}`);
const data = await response.json();
setProperties(data.data.properties);
setPagination({
page: data.data.page,
totalPages: data.data.totalPages,
total: data.data.total,
});
} catch (err) {
setError(err.message);
setProperties([]);
} finally {
setLoading(false);
}
}, []);
return { properties, loading, error, pagination, searchProperties };
}
Location Autocomplete Hook
Build a hook for location typeahead search:
// src/hooks/useAutocomplete.js
import { useState, useCallback } from "react";
import { API_CONFIG } from "../config/api";
export function useAutocomplete() {
const [suggestions, setSuggestions] = useState([]);
const [loading, setLoading] = useState(false);
const search = useCallback(async (query) => {
if (!query || query.length < 2) {
setSuggestions([]);
return;
}
setLoading(true);
try {
const params = new URLSearchParams({ query, langs: "en" });
const response = await fetch(
`${API_CONFIG.baseUrl}/autocomplete?${params}`,
{ headers: API_CONFIG.headers }
);
if (!response.ok) throw new Error("Autocomplete failed");
const data = await response.json();
setSuggestions(
data.data.locations.map((hit) => ({
id: hit.externalID,
name: hit.name,
type: hit.type,
}))
);
} catch {
setSuggestions([]);
} finally {
setLoading(false);
}
}, []);
return { suggestions, loading, search };
}
Advanced Example: Property Search Component
Put it all together in a complete search interface:
// src/components/PropertySearch.jsx
import { useState } from "react";
import { usePropertySearch } from "../hooks/usePropertySearch";
import { useAutocomplete } from "../hooks/useAutocomplete";
function PropertyCard({ property }) {
return (
<div style={{ border: "1px solid #333", borderRadius: 8, padding: 16, marginBottom: 12 }}>
<h3>{property.title.en}</h3>
<p style={{ fontSize: 20, fontWeight: "bold" }}>
AED {property.price?.toLocaleString()}
</p>
<p>
{property.bedrooms} bed · {property.bathrooms} bath · {property.area} sqft
</p>
<p style={{ color: "#888" }}>{property.location}</p>
</div>
);
}
export default function PropertySearch() {
const [locationQuery, setLocationQuery] = useState("");
const [selectedLocation, setSelectedLocation] = useState(null);
const { suggestions, search: searchLocations } = useAutocomplete();
const { properties, loading, error, pagination, searchProperties } = usePropertySearch();
const handleLocationInput = (e) => {
const value = e.target.value;
setLocationQuery(value);
searchLocations(value);
};
const handleSelectLocation = (location) => {
setSelectedLocation(location);
setLocationQuery(location.name);
searchProperties({ locationId: location.id, purpose: "for-sale" });
};
const handlePageChange = (page) => {
if (selectedLocation) {
searchProperties({ locationId: selectedLocation.id, purpose: "for-sale", page });
}
};
return (
<div style={{ maxWidth: 800, margin: "0 auto", padding: 20 }}>
<h1>Property Search</h1>
<div style={{ position: "relative", marginBottom: 20 }}>
<input
type="text"
value={locationQuery}
onChange={handleLocationInput}
placeholder="Search for a location..."
style={{ width: "100%", padding: 12, fontSize: 16 }}
/>
{suggestions.length > 0 && (
<ul style={{ position: "absolute", top: "100%", left: 0, right: 0, background: "#1a1a1a", border: "1px solid #333", listStyle: "none", padding: 0, margin: 0, zIndex: 10 }}>
{suggestions.map((loc) => (
<li key={loc.id} onClick={() => handleSelectLocation(loc)} style={{ padding: 12, cursor: "pointer", borderBottom: "1px solid #333" }}>
{loc.name} <span style={{ color: "#888" }}>({loc.type})</span>
</li>
))}
</ul>
)}
</div>
{loading && <p>Loading properties...</p>}
{error && <p style={{ color: "red" }}>Error: {error}</p>}
{pagination.total > 0 && (
<p>{pagination.total.toLocaleString()} properties found</p>
)}
{properties.map((prop) => (
<PropertyCard key={prop.id} property={prop} />
))}
{pagination.totalPages > 1 && (
<div style={{ display: "flex", gap: 8, justifyContent: "center", marginTop: 20 }}>
<button onClick={() => handlePageChange(pagination.page - 1)} disabled={pagination.page <= 1}>
Previous
</button>
<span>Page {pagination.page} of {pagination.totalPages}</span>
<button onClick={() => handlePageChange(pagination.page + 1)} disabled={pagination.page >= pagination.totalPages}>
Next
</button>
</div>
)}
</div>
);
}
Next Steps
- Explore the Next.js Integration Guide for server-side rendering and API routes
- Browse the full API documentation for all available filter parameters
- Check out the Property Listing Websites use case for architecture guidance
- Add debouncing to the autocomplete input for better performance (consider
useDeferredValueor a debounce utility)