Level 1 Team Project

1. House Real Estate Simulator

This guide is tailored for students who want to build an House Real Estate like application using React and Vite frameworks, based on the files and designs you've shared.

2. Initialize the Project

1. Create a new React + Vite project:

npm create vite@latest haunted-house-app -- --template react
cd haunted-house-app
npm install

2. Install dependencies (if you use additional ones like React Router or icons):

Step 1: Set Up Project Structure

  1. Structure your folders as follows:

/src
  /components
    HouseCard.jsx
    Footer.jsx
    Message.jsx
  App.jsx
  main.jsx
/style.css
/index.html

Step 2: Create a new component

  • Created a new file: src/components/HouseCard.jsx

  • Moved the JSX code from your original App.jsx into this new HouseCard component.

  • Passed the necessary data as three props only:

    • index: the current house number.

    • total: total number of houses.

    • houseData: all house details (price, location, etc.) as an object.

// File: components/HouseCard.jsx
import React from "react"

export default function HouseCard({ index, total, houseData }) {
    // Prepare list items dynamically
    const features = [
        { label: "Price", value: houseData.price },
        { label: "Location", value: houseData.location },
        { label: "Square Feet", value: houseData.squareFeet },
        { label: "Acres", value: houseData.acres },
        { label: "Year Built", value: houseData.yearBuilt },
        { label: "Bedrooms", value: houseData.bedrooms },
        { label: "Bathrooms", value: houseData.bathrooms },
        { label: "Other Rooms", value: houseData.otherRooms },
        { label: "Garage", value: houseData.garage ? "Yes" : "No" },
        { label: "Air Conditioning", value: houseData.airConditioning ? "Yes" : "No" },
        { label: "Heating", value: houseData.heating ? "Yes" : "No" },
        { label: "Haunted", value: houseData.haunted ? "Yes" : "No" },
    ]

    return (
        <div className="house-card" key={houseData.id}>
            <p>Listing {index + 1} of {total}</p>
            <img src={houseData.image} alt={`House in ${houseData.location}`} />
            <div>
                <ul>
                    {features.map((item, idx) => (
                        <li key={idx}>
                            <span>{item.label}: {item.value} </span>
                        </li>
                    ))}
                </ul>
            </div>
        </div>
    )
}

Create data foler at root directory and add houseForSale.js data

// housesForSale.js
import {nanoid} from "nanoid"

export default [
    {
        id: nanoid(),
        image: "./images/eleanor-brooke-62ZrBo3PoKc-unsplash.jpg",
        price: "$1,342,000",
        location: "Podunk, Ohio",
        squareFeet: "3,752",
        acres: 4.7,
        bedrooms: 5,
        bathrooms: 4,
        otherRooms: 6,
        yearBuilt: 1902,
        garage: false,
        airConditioning: false,
        heating: true,
        haunted: true,
    },{
        id: nanoid(),
        image: "./images/luis-muller-t1IcKA8HkUM-unsplash.jpg",
        price: "$152,000",
        location: "Stumpsville, Indiana",
        squareFeet: "1,252",
        acres: 1.4,
        bedrooms: 3,
        bathrooms: 2,
        otherRooms: 2,
        yearBuilt: 1932,
        garage: false,
        airConditioning: true,
        heating: true, 
        haunted: true, 
    },{
        id: nanoid(),
        image: "./images/amber-kipp-DJEkBfLp6bc-unsplash.jpg",
        price: "$233,000",
        location: "Backwater, Tennessee",
        squareFeet: "1,995",
        acres: 2.6,
        bedrooms: 4,
        bathrooms: 4,
        otherRooms: 2,
        yearBuilt: 1877,
        garage: true,
        airConditioning: true,
        heating: true,
        haunted: true,
    },{
        id: nanoid(),
        image: "./images/nathan-dumlao-Mw1JgIAuK6c-unsplash.jpg",
        price: "$176,500",
        location: "Sticksville, Vermont",
        squareFeet: "956",
        acres: 12.6,
        bedrooms: 2,
        bathrooms: 1,
        otherRooms: 1,
        yearBuilt: 1916,
        garage: false,
        airConditioning: false,
        heating: false,
        haunted: true,
    },{
        id: nanoid(),
        image: "./images/robbie-down-3IRIerl16nk-unsplash.jpg",
        price: "$142,000",
        location: "Hinterland, Virginia",
        squareFeet: "1,212",
        acres: 9.4,
        bedrooms: 3,
        bathrooms: 2,
        otherRooms: 1,
        yearBuilt: 1925,
        garage: false,
        airConditioning: false,
        heating: true,
        haunted: true,
    },{
        id: nanoid(),
        image: "./images/peter-herrmann-eZaEWy2rAIc-unsplash.jpg",
        price: "$380,120",
        location: "Backwoods, Oregon",
        squareFeet: "2,612",
        acres: 5.2,
        bedrooms: 5,
        bathrooms: 4,
        otherRooms: 4,
        yearBuilt: 1903,
        garage: true,
        airConditioning: true,
        heating: true,
        haunted: true,
    },
    
]

Step 3: Dynamically generate list items

  • Inside HouseCard, we use .map() to dynamically iterate over an array of objects containing label and value pairs (e.g., Price, Location, etc.).

  • No more hard-coded <li> elements!

Step 4: Update App.jsx

  • Import HouseCard into App.jsx.

  • Replace the map() function's JSX with the new <HouseCard /> component.

  • Pass props neatly.

// File: App.jsx
import React from "react"
import housesForSale from "../data/housesForSale"
import HouseCard from "./components/HouseCard"

export default function App() {
    return (
        <div className="wrapper">
            <header>
                <img className="logo" src="images/logo.png" alt="Haunted House Real Estate Logo" />
            </header>
            <div className="house-cards-container">
                {housesForSale.map((houseData, index, array) => (
                    <HouseCard 
                        key={houseData.id}
                        index={index} 
                        total={array.length} 
                        houseData={houseData} 
                    />
                ))}
            </div>
        </div>
    )
}

Step 5: Maintain Visual Style

  • Your CSS (style.css) remains the same. So, the visual output does not change.

  • Design looks exactly like your provided UI screenshot!

* {
    box-sizing: border-box
}


body { 
    margin: 0;
}

header {
    width: 100vw;
    display: flex;
    justify-content: center;
    padding: 15px;
    background: #495057;
    position: fixed;
    box-shadow: 2px 1px 4px 1px #212529;
}

.logo {
    height: 100px;
    border: 1px solid #212529;
    box-shadow: 1px 1px 4px 1px #212529;
    border-radius: 15px;
    background: #dee2e6;
    padding: 0 60px;
}

.wrapper {
    background: hsla(210, 14%, 83%, 1);
    background: linear-gradient(270deg, hsla(210, 14%, 83%, 1) 0%, hsla(208, 7%, 46%, 1) 100%);
    background: -moz-linear-gradient(270deg, hsla(210, 14%, 83%, 1) 0%, hsla(208, 7%, 46%, 1) 100%);
    background: -webkit-linear-gradient(270deg, hsla(210, 14%, 83%, 1) 0%, hsla(208, 7%, 46%, 1) 100%);
    background-attachment: fixed;
}

.house-cards-container {
    padding-top: 150px;
    display: flex;
    justify-content: center;
    width: 100vw;
    flex-wrap: wrap;
    gap: 30px;
    padding-bottom: 30px;
}

.house-card {
    width: 400px;
    height: 760px;
    border-radius: 15px;
    background: #f8f9fa;
    display: flex;
    flex-direction: column;
    padding: 30px;
    border: 2px solid #212529;
    box-shadow: 2px 2px 4px 1px #212529;
}

.house-card p {
    margin: -10px 0 10px 0;
    text-align: center;
    font-weight: 600;
    font-size: 16px;
}

.house-card img {
    width: 340px;
    height: 340px;
    object-fit: cover;
    border-radius: 15px;
    align-self: center;
    margin-bottom: 20px;
    border: 2px solid #212529;
}

.house-card ul {
    display: flex;
    width: 100%;
    flex-wrap: wrap;
    justify-content: space-between;
    height: 250px;
    gap: 20px 60px;
    font-size: 16px;
}

ul {
    margin: 0;
    padding: 0;
}

li {
    list-style: none;
}

li:nth-of-type(even) {
    text-align: end;
}

span {
    font-weight: 700;
    display: block;
}

Output:

Optional (Next Level Enhancements)

If you want to go further:

  1. PropTypes → Add type checking for props.

  2. Add animations → Hover effects or transitions.

  3. Responsive layout → Make it mobile-friendly.

  4. Fetch data dynamically → From an API or external JSON file.

Challenge: Fetch data dynamically → From an API or external JSON file.

We are now moving toward a real external property API integration. Let’s go step by step. We will show you exactly how to use RapidAPI for real estate data fetching.

Step 1: Sign Up and Get API Key on RapidAPI

  1. Create an account (free tier is fine). You need a credit card for creating API endpoint.

  2. Search for:

    • Zillow API (great for U.S. properties)

    • Realtor API (good for listings, agents, property photos)

  3. Subscribe to the API.

  4. Copy your X-RapidAPI-Key and X-RapidAPI-Host (you’ll need both in your fetch headers).

Step 2: Example Code Integration (React + RapidAPI)

Let’s assume you pick Zillow API (easy & fast). For secure management of your API key, we need a separate file named .env at the root directory. Replace 'YOUR_API_KEY_HERE' with your actual RapidAPI key.

// .env
VITE_API_KEY = <your API Key>

Update your useEffect() in App.jsx like this:

// Updated App.jsx
import React, { useEffect, useState } from 'react'
import HouseCard from './components/HouseCardFetch'

export default function App() {
  const [housesForSale, setHousesForSale] = useState([])

  // Use environment variable for API key if available
  const apiKey = import.meta.env.VITE_API_KEY || 'YOUR_API_KEY'

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          'https://zillow69.p.rapidapi.com/search?location=Houston%2C%20TX&status_type=ForSale&daysOn=1&soldInLast=1',
          {
            method: 'GET',
            headers: {
              'X-RapidAPI-Key': apiKey,
              'X-RapidAPI-Host': 'zillow69.p.rapidapi.com',
            },
          },
        )
        const data = await response.json()
        console.log(data.props) // Check structure
        setHousesForSale(data.props) // Adjust based on response structure
      } catch (error) {
        console.error('Error fetching houses:', error)
      }
    }

    fetchData()
  }, [])

  return (
    <div className="wrapper">
      <header>
        <img
          className="logo"
          src="images/logo.png"
          alt="Haunted House Real Estate Logo"
        />
        {error && (
          <div className="error-banner">
            <p>API Error: {error}. Showing sample listings instead.</p>
          </div>
        )}
      </header>

      <div className="house-cards-container">
        {housesForSale.map((houseData, index, array) => (
          <HouseCard
            key={index}
            index={index}
            total={array.length}
            houseData={houseData}
          />
        ))}
      </div>
    </div>
  )
}

Step 3: Adjust Data Mapping

Most APIs will return data in a slightly different shape than your local JSON. So you might need to map API fields to your UI.

For example, in your <HouseCard /> props, update like this:

Update your HouseCardFetch.jsx like this:

// components/HouseCardFetch.jsx

import React from 'react'

export default function HouseCard({ index, total, houseData }) {
  const features = [
    { label: 'Price', value: houseData.price || houseData.listPrice},
{ label: 'Location', value: houseData.address || houseData.address?.city},
    { label: 'Square Feet', value: houseData.livingArea || houseData.squareFeet },
    { label: 'Property Type', value: houseData.propertyType },
    { label: 'Listing Status', value: houseData.listingStatus },
    { label: 'Bedrooms', value: houseData.bedrooms },
    { label: 'Bathrooms', value: houseData.bathrooms },
    { label: 'Garage', value: houseData.garage ? 'Yes' : 'No' },
    {
      label: 'Air Conditioning',
      value: houseData.airConditioning ? 'Yes' : 'No'},
    { label: 'Heating', value: houseData.heating ? 'Yes' : 'No' },
    { label: 'Haunted', value: houseData.haunted ? 'Yes' : 'No' },
  ]

  return (
    <div className="house-card " >
      <p>
        Listing {index + 1} of {total}
      </p>
      <img
        src={houseData.imgSrc}
        alt={`House in ${houseData.zipcode}`}
      />
      <div>
        <ul>
          {features.map((item, idx) => (
            <li key={idx}>
              <span>{item.label}: {item.value} </span>
            </li>
          ))}
        </ul>
      </div>
    </div>
  )
}

Step 4: Test

  • Start your app: npm run dev (or yarn).

  • Open your browser console to see the fetched data.

  • Map it cleanly to your UI 🎉

Output:

We would like to help you add dynamic search functionality next? 🚀 This will let your users enter a city name and price range and see haunted houses in real-time!

Our app is now leveled up with dynamic search inputs for:

  • 🏙️ City name

  • 💰 Minimum price

  • 💰 Maximum price

Update your AppFetchFilter.jsx like this:

// Updated AppFetchFilter.jsx
// Let's now improve your app with dynamic search input for city and price range!
// We'll update App.jsx to include search filters.

import React, { useEffect, useState } from 'react'
import HouseCard from './components/HouseCardFetch'

export default function App() {
  const [housesForSale, setHousesForSale] = useState([])
  const [city, setCity] = useState('Houston') // Default city
  const [minPrice, setMinPrice] = useState(0)
  const [maxPrice, setMaxPrice] = useState(1000000)

  // Use environment variable for API key if available
  const apiKey = import.meta.env.VITE_API_KEY || 'YOUR_API_KEY'

  const fetchData = async () => {
    try {
      const response = await fetch(
        `https://zillow69.p.rapidapi.com/search?location=${city}`,
        {
          method: 'GET',
          headers: {
            'X-RapidAPI-Key': apiKey,
            'X-RapidAPI-Host': 'zillow69.p.rapidapi.com',
          },
        },
      )
      const data = await response.json()
      console.log(data)
      // Filter data based on price range
      const filteredData = data.props.filter(
        (house) => house.price >= minPrice && house.price <= maxPrice,
      )
      setHousesForSale(filteredData)
    } catch (error) {
      console.error('Error fetching houses:', error)
    }
  }

  useEffect(() => {
    fetchData()
  }, [city, minPrice, maxPrice]) // Fetch data when filters change

  return (
    <div className="wrapper">
      <header>
        <img
          className="logo"
          src="images/logo.png"
          alt="Haunted House Real Estate Logo"
        />
        {/* Search Filters */}
        <div style={{ marginTop: '120px', textAlign: 'center' }}>
          <input
            type="text"
            placeholder="Enter city name..."
            value={city}
            onChange={(e) => setCity(e.target.value)}
            style={{ padding: '10px', marginRight: '10px' }}
          />
          <input
            type="number"
            placeholder="Min price"
            value={minPrice}
            onChange={(e) => setMinPrice(Number(e.target.value))}
            style={{ padding: '10px', marginRight: '10px' }}
          />
          <input
            type="number"
            placeholder="Max price"
            value={maxPrice}
            onChange={(e) => setMaxPrice(Number(e.target.value))}
            style={{ padding: '10px', marginRight: '10px' }}
          />
          <button onClick={fetchData} style={{ padding: '10px 20px' }}>
            Search
          </button>
        </div>
      </header>

      {/* House Listings */}
      <div className="house-cards-container">
        {housesForSale.length > 0 ? (
          housesForSale.map((houseData, index, array) => (
            <HouseCard
              key={houseData.id || index}
              index={index}
              total={array.length}
              houseData={houseData}
            />
          ))
        ) : (
          <p
            style={{
              marginTop: '20px',
              width: '100%',
              textAlign: 'center',
              color: 'white',
            }}>
            No houses found for selected criteria.
          </p>
        )}
      </div>
    </div>
  )
}

// HouseCardFetch.jsx stays the same ✅

// 🎉 Now your app has:
// - City input search
// - Price range filter (min and max price)
// - Fetches live data based on user input!
//
// Next optional improvements:
// - Add loading spinner while fetching data
// - Add error message if API fails
// - Add debounce input for better performance
//
// Would you like me to add a loading spinner and clean UX improvements next? 🚀

Output:

What I improved:

  • Smooth Debounce ➔ Delayed API calls while typing, for better performance.

  • Styled Inputs & Buttons ➔ Rounded corners, better spacing, colors that match your theme.

  • Loading State ➔ User sees "Loading houses..." during fetch.

  • Error Handling ➔ Friendly error message if API call fails.

  • Sticky Beautiful Search Bar ➔ Stays visible and styled even when scrolling.

🔥 Bonus — Helpful APIs on RapidAPI

API Name
Link
Notes

Realtor API

RapidAPI Link

Popular, extensive U.S. real estate data.

Zillow API

RapidAPI Link

Well-known brand, U.S. focus.

Domain API (Australia)

RapidAPI Link

If you want Australian properties.

Last updated