Code To Learn logo

Code To Learn

M2: State & Events

L1: Converting Listings to State

Transform static data into React state using the useState hook

State Management useState Hook

In Module 1, we built StaySense with hardcoded listings data. Now it's time to make our application dynamic and interactive by introducing React state.


📝 Data Structure Update

Before we begin, note that our listing data structure has evolved:

Module 1 (Learning basics):

  • Simple properties: id, title, price, location, image, rating

Module 2+ (Building real app):

  • Added: guests, bedrooms (needed for booking functionality)
  • Removed: rating (we'll add a complete review system in Module 7)

This is normal in real projects - data structures evolve as features grow! You don't need to change your Module 1 code. We're just expanding the data model for our booking app.


Why Do We Need State?

Right now, our HomePage component has a static array of listings:

src/pages/HomePage.jsx (Current - Static)
const listings = [
  {
    id: 1,
    title: "Cozy Beach House",
    price: 250,
    location: "Malibu, CA"
  },
  // More listings...
];

Problem: This data never changes. We can't:

  • Filter listings based on search criteria
  • Add or remove listings dynamically
  • Update prices or availability
  • Make the app respond to user interactions

Solution: Convert the array to React state using the useState hook.

Key Concept: State is data that can change over time. When state changes, React automatically re-renders your component to reflect the new data.


Understanding useState

The useState hook is React's way of giving components memory:

useState Syntax
import { useState } from 'react';

function Component() {
  const [value, setValue] = useState(initialValue); 
  
  // value: current state value
  // setValue: function to update state
  // initialValue: starting value
  
  return <div>{value}</div>;
}

Key Points:

  1. Import from React: useState is a named export
  2. Array Destructuring: Returns [currentValue, updaterFunction]
  3. Initial Value: Only used on first render
  4. Updater Function: Always starts with "set" by convention

Important: The updater function doesn't modify the existing state - it replaces it with a new value. State is immutable.


Step-by-Step Implementation

Step 1: Import useState

Open src/pages/HomePage.jsx and add the useState import:

src/pages/HomePage.jsx
import { useState } from 'react'; 
import PropertyCard from '../components/PropertyCard';

export function HomePage() {
  // Component code...
}

Why at the top? All React hooks must be imported before they're used.

Step 2: Replace Static Array with State

Find the hardcoded listings array and convert it to state:

src/pages/HomePage.jsx
export function HomePage() {
  // ❌ Old: Static array (can't change)
  const listings = [ 
    { 
      id: 1, 
      title: "Cozy Beach House", 
      price: 250, 
      location: "Malibu, CA", 
      image: "/images/beach-house.jpg", 
    }, 
    { 
      id: 2, 
      title: "Mountain Cabin", 
      price: 180, 
      location: "Aspen, CO", 
      image: "/images/cabin.jpg", 
    }, 
    { 
      id: 3, 
      title: "Downtown Loft", 
      price: 320, 
      location: "New York, NY", 
      image: "/images/loft.jpg", 
    }, 
  ]; 
  
  // ✅ New: State (can be updated dynamically)
  const [listings, setListings] = useState([ 
    { 
      id: 1, 
      title: "Cozy Beach House", 
      price: 250, 
      location: "Malibu, CA", 
      image: "/images/beach-house.jpg", 
    }, 
    { 
      id: 2, 
      title: "Mountain Cabin", 
      price: 180, 
      location: "Aspen, CO", 
      image: "/images/cabin.jpg", 
    }, 
    { 
      id: 3, 
      title: "Downtown Loft", 
      price: 320, 
      location: "New York, NY", 
      image: "/images/loft.jpg", 
    }, 
  ]); 
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">Available Stays</h1>
      
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {listings.map(listing => (
          <PropertyCard key={listing.id} listing={listing} />
        ))}
      </div>
    </div>
  );
}

What Changed?

  • const listings = [...]const [listings, setListings] = useState([...])
  • Same data, now managed by React state
  • setListings function ready for future updates

Step 3: Verify Nothing Broke

Save the file and check your browser. The page should look exactly the same.

Good Sign! We've refactored to state without breaking the UI. The component still renders the same listings.

Why no visible change?

We converted to state but haven't used the setListings function yet. The listings display the same because we passed the same initial data.


Understanding State vs. Regular Variables

Why use state instead of a regular variable? Let's compare:

❌ Regular Variable (Doesn't Work)

Won't Trigger Re-render
function Component() {
  let count = 0; // Regular variable
  
  const increment = () => {
    count = count + 1; // Value changes...
    console.log(count); // Logs the new value...
    // But UI doesn't update! ❌
  };
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

Problem: Changing count doesn't tell React to re-render. The UI stays frozen at 0.

✅ State Variable (Works Perfectly)

Triggers Re-render
import { useState } from 'react';

function Component() {
  const [count, setCount] = useState(0); // State variable
  
  const increment = () => {
    setCount(count + 1); // Update state
    // React re-renders with new value ✅
  };
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

Solution: Calling setCount tells React to re-render with the new value.

React's Golden Rule: Components re-render when:

  1. State changes (via setState functions)
  2. Props change (parent passes new props)
  3. Parent re-renders (React re-renders children)

Complete HomePage Code

Here's your complete HomePage component with state:

src/pages/HomePage.jsx
import { useState } from 'react';
import PropertyCard from '../components/PropertyCard';

export function HomePage() {
  const [listings, setListings] = useState([ 
    {
      id: 1,
      title: "Cozy Beach House",
      price: 250,
      location: "Malibu, CA",
      image: "/images/beach-house.jpg",
      guests: 4,
      bedrooms: 2,
      bathrooms: 2,
    },
    {
      id: 2,
      title: "Mountain Cabin",
      price: 180,
      location: "Aspen, CO",
      image: "/images/cabin.jpg",
      guests: 6,
      bedrooms: 3,
      bathrooms: 2,
    },
    {
      id: 3,
      title: "Downtown Loft",
      price: 320,
      location: "New York, NY",
      image: "/images/loft.jpg",
      guests: 2,
      bedrooms: 1,
      bathrooms: 1,
    },
  ]); 
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">Available Stays</h1>
      
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {listings.map(listing => (
          <PropertyCard key={listing.id} listing={listing} />
        ))}
      </div>
    </div>
  );
}

State Management Best Practices

1. Naming Convention

Always name state variables and their updaters consistently:

const [user, setUser] = useState(null);
const [count, setCount] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [items, setItems] = useState([]);

// Pattern: [noun, setNoun] or [isCondition, setIsCondition]

2. Initialize with Correct Data Type

const [count, setCount] = useState(0);        // Number
const [name, setName] = useState('');         // String
const [isOpen, setIsOpen] = useState(false);  // Boolean
const [items, setItems] = useState([]);       // Array
const [user, setUser] = useState(null);       // Object (or null)

3. Don't Mutate State Directly

// ❌ Wrong: Mutating state
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // Don't do this!
setItems(items);

// ✅ Correct: Create new array
setItems([...items, 4]); // Create new array with spread

Remember: State is immutable. Always create new objects/arrays.


Checkpoint


What's Next?

In Lesson 2, we'll create the ListingFilters component with search input, date pickers, and guest controls. You'll learn how to handle user input and manage multiple pieces of state.

Coming up:

  • Event handlers (onChange, onClick)
  • Controlled inputs
  • Multiple state variables
  • Component communication

Great job converting to state! 🎉