1

JSX — JavaScript XML

React and JSX

React is a free, open-source front-end JavaScript library maintained by Meta (formerly Facebook) for building user interfaces.

React has a declarative nature: you describe what you want the UI to look like, and React handles the steps to achieve that UI and keep it updated efficiently when state changes. This is in contrast to imperative programming (plain JavaScript), which focuses on how to achieve a result by manually manipulating UI elements.

JSX — JavaScript XML

JSX is a syntax extension that allows developers to write HTML-like code within JavaScript. This makes it easier to define the structure and content of UI components.

const propertyName = "Royce Hall";
// [...]
return <h1>Welcome to {propertyName}!</h1>;
  • Use curly braces { } to embed any JavaScript expression inside JSX
  • Babel compiles this JSX to plain JavaScript:
    React.createElement('h1', null, 'Welcome to ', propertyName, '!')
    
  • The result is a lightweight Virtual DOM object — not a real DOM node yet

Task

The property name and price are hard-coded strings. Create variables propertyName and price, then use { } curly braces to embed them in the JSX instead.

Starter files
step1/App.jsx
// JSX: write HTML-like syntax directly in JavaScript
// Use { } curly braces to embed JavaScript expressions

const gameName = "UCLA Monopoly";
// TODO: Create a propertyName variable set to "Royce Hall"
// TODO: Create a price variable set to 1000
// TODO: Replace the hard-coded text below with {propertyName} and ${price}

function App() {
  return (
    <div style={{ padding: '16px', fontFamily: "'Georgia', serif" }}>
      <h1>Welcome to UCLA Monopoly!</h1>
      <p>Most expensive property: Royce Hall</p>
      <p>Price: $1000</p>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
2

The Map Function — Rendering Lists

Rendering Lists with .map()

In React, you render lists by using JavaScript’s built-in .map() function directly inside JSX.

const properties = [
  { name: "Royce Hall", price: 1000 },
  { name: "Powell Library", price: 500 },
];
// [...]
return (<ul>
  {properties.map((prop, index) => (
    <li key={index}>{prop.name} — ${prop.price}</li>
  ))}
</ul>);
  • properties.map(...) returns an array of JSX elements — React renders all of them as children of <ul>
  • The key prop is required on each list element so React can efficiently track which items changed, were added, or were removed
  • key must be unique within the list — here we use the array index, but in production prefer a stable ID from your data

Task

The properties array is defined but the list is empty. Use properties.map() to render each property as a <li> element showing its name and price. Don’t forget the key prop!

Starter files
step2/App.jsx
function App() {
  const properties = [
    { name: "Royce Hall", price: 1000 },
    { name: "Powell Library", price: 500 },
    { name: "Kerckhoff Hall", price: 60 },
  ];

  return (
    <div style={{ padding: '16px', fontFamily: "'Georgia', serif" }}>
      <h2>UCLA Monopoly  Property Listing</h2>
      <ul>
        {/* TODO: Use properties.map() to render each property */}
        {/* Each <li> should show: name — $price */}
        {/* Don't forget the key prop! */}
      </ul>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
3

Components & Props — Monopoly Properties

Components & Props

Components are self-contained, reusable pieces of UI. In our UCLA Monopoly game, every property card has the same structure — a colored banner, a name, and a price — but the data differs.

The Problem: Hard-Coded Cards

The current code has a single hard-coded card for Royce Hall. Want three cards? You’d have to copy-paste and change every value by hand.

The Solution: A Reusable Component with Props

Props (short for properties) are read-only inputs passed to a component — just like function arguments. Extract the card into a component and make the varying parts props:

const MonopolyProperty = ({ name, price, colorGroup }) => {
  return (
    <div style={cardStyle}>
      <div style={getHeaderStyle(colorGroup)}></div>
      <div style={bodyStyle}>
        <h4>{name}</h4>
        <p><strong>${price}</strong></p>
      </div>
    </div>
  );
};

Now rendering three properties is clean — same component, different data:

<MonopolyProperty name="Royce Hall" price={1000} colorGroup="ucla-blue" />
<MonopolyProperty name="Powell Library" price={500} colorGroup="ucla-blue" />
  • { name, price, colorGroup } destructures the props object
  • Props are read-only — a component must never modify its own props
  • The styles live in styles.js — the component only handles structure and data

Task

Extract a reusable MonopolyProperty component that takes name, price, and colorGroup as props. Then render three different UCLA property cards side by side.

Starter files
step3/styles.js
// Monopoly color groups — maps a group name to its banner color
const colorGroups = {
  "ucla-blue":  "#2774AE",
  "ucla-gold":  "#FFD100",
};

// Shared card styles for every property tile
const cardStyle = {
  width: "180px",
  border: "3px solid #333",
  borderRadius: "12px",
  overflow: "hidden",
  margin: "8px",
  fontFamily: "'Georgia', serif",
  background: "#F5F0E1",
  color: "#333",
  boxShadow: "2px 4px 10px rgba(0,0,0,0.2)",
};

// Returns the colored header banner for a given color group
function getHeaderStyle(colorGroup) {
  return {
    height: "45px",
    background: colorGroups[colorGroup] || "#ccc",
  };
}

const bodyStyle = {
  padding: "12px",
  textAlign: "center",
};
step3/App.jsx
// This card is hard-coded for Royce Hall — NOT reusable!
// TODO: Extract a MonopolyProperty component with props:
//       name, price, colorGroup
// TODO: Render three property cards with different data
function App() {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', padding: '16px' }}>
      <div style={cardStyle}>
        <div style={getHeaderStyle("ucla-blue")}></div>
        <div style={bodyStyle}>
          <h4>Royce Hall</h4>
          <p><strong>$1000</strong></p>
        </div>
      </div>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
4

State & useState — Buying Houses

State — Components That Remember

Props are read-only inputs. But what if a component needs to track something that changes over time? In Monopoly, players buy houses to upgrade their properties — the number of houses changes with each purchase.

React provides the useState hook:

const [houses, setHouses] = useState(0);
//      ↑ current value    ↑ setter   ↑ initial value
  • useState(0) returns a pair: [currentValue, setter]
  • Calling the setter — setHouses(houses + 1) — updates the value and triggers a re-render
  • When houses reaches 5, the houses upgrade to a hotel!

Event Handlers

A button triggers buyHouse when clicked:

const buyHouse = () => {
  setHouses(houses + 1);
};
// ...
<button onClick={buyHouse}>Buy House</button>

onClick={buyHouse} passes the function reference — React calls it when the user clicks. Do not write onClick={buyHouse()} — that calls the function immediately during render.

Visual Feedback

The renderBuildings(houses) helper converts the house count into icons:

  • 0 houses → “No buildings”
  • 1–4 houses → house icons
  • 5 houses → hotel icon (upgrade!)

Task

The property cards are static — clicking the button does nothing. Add a [houses, setHouses] state variable with useState(0), implement the buyHouse handler that increments houses up to MAX_HOUSES + 1, and display the buildings with renderBuildings(houses).

Starter files
step4/styles.js
// Monopoly color groups
const colorGroups = {
  "ucla-blue":  "#2774AE",
  "ucla-gold":  "#FFD100",
};

const cardStyle = {
  width: "200px",
  border: "3px solid #333",
  borderRadius: "12px",
  overflow: "hidden",
  margin: "8px",
  fontFamily: "'Georgia', serif",
  background: "#F5F0E1",
  color: "#333",
  boxShadow: "2px 4px 10px rgba(0,0,0,0.2)",
};

function getHeaderStyle(colorGroup) {
  return {
    height: "45px",
    background: colorGroups[colorGroup] || "#ccc",
  };
}

const bodyStyle = {
  padding: "12px",
  textAlign: "center",
};

const buttonStyle = {
  padding: "6px 14px",
  border: "2px solid #333",
  borderRadius: "6px",
  background: "#4CAF50",
  color: "white",
  fontWeight: "bold",
  cursor: "pointer",
  marginTop: "8px",
  fontSize: "13px",
};

// Building icons
const HOUSE_EMOJI = "\uD83C\uDFE0";
const HOTEL_EMOJI = "\uD83C\uDFE8";
step4/App.jsx
const { useState } = React;

const MAX_HOUSES = 4;

// Converts house count to visual building icons
function renderBuildings(houses) {
  if (houses === 0) return "No buildings";
  if (houses > MAX_HOUSES) return HOTEL_EMOJI;
  return HOUSE_EMOJI.repeat(houses);
}

// TODO: Add state: const [houses, setHouses] = useState(0);
// TODO: Add buyHouse handler that increments houses (up to MAX_HOUSES + 1)
// TODO: Use renderBuildings(houses) to display buildings
// TODO: Wire up onClick={buyHouse} on the button
const MonopolyProperty = ({ name, price, colorGroup }) => {
  return (
    <div style={cardStyle}>
      <div style={getHeaderStyle(colorGroup)}></div>
      <div style={bodyStyle}>
        <h4>{name}</h4>
        <p><strong>${price}</strong></p>
        <div style={{ fontSize: "24px", minHeight: "36px" }}>
          No buildings
        </div>
        <button style={buttonStyle}>
          Buy House ($50)
        </button>
      </div>
    </div>
  );
};

function App() {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', padding: '16px' }}>
      <MonopolyProperty name="Royce Hall" price={1000} colorGroup="ucla-blue" />
      <MonopolyProperty name="Powell Library" price={500} colorGroup="ucla-blue" />
      <MonopolyProperty name="Kerckhoff Hall" price={60} colorGroup="ucla-gold" />
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
5

Virtual DOM — Complete UCLA Monopoly

The Virtual DOM

React employs a Virtual DOM — a lightweight JavaScript representation of the actual DOM.

When data changes, React:

  1. First updates the Virtual DOM (fast — it is just a JavaScript object tree)
  2. Calculates the differences between the new and previous Virtual DOM
  3. Applies only the necessary changes to the real DOM

This minimizes direct DOM manipulation and improves performance.

The game below puts everything together: JSX, .map(), components, props, and useState. When you buy a property or build a house, React only updates the specific card and wallet — not the entire page. That is the Virtual DOM in action.

Separation of Concerns

Notice how the code is organized into three separate modules:

Module Layer Responsibility
monopoly-game.js Application Game data, rules, and state transitions — pure JavaScript, no React
PropertyCard.jsx Presentation Renders a single property card from props — pure UI, no game logic
App.jsx Wiring Connects game logic to UI via React state

This is the Separation of Concerns principle: each module has a single, well-defined responsibility. The game logic knows nothing about React or the DOM. The UI component knows nothing about game rules. Benefits:

  • Testabilitymonopoly-game.js can be unit-tested without rendering any UI
  • ReusabilityPropertyCard can display any property data, from any source
  • Maintainability — changing a game rule doesn’t require touching UI code, and vice versa

Single Page Application (SPA)

SPAs are structured as a single HTML page with no preloaded content. Content is loaded dynamically via JavaScript:


@startuml
participant c: Client
participant s: Server

c -> s: GET '/'
activate s
s --> c: Single Page App (contains all pages)
deactivate s
c -> s: GET /current_news
activate s
s --> c: JSON data
deactivate s
c -> s: GET /about
activate s
s --> c: JSON data
deactivate s
@enduml

React is the most common technology for building SPAs. Navigation happens client-side — no full page reloads.

Server-Side Rendering (SSR)

Frameworks like Next.js (which uses React) do SSR. Clients receive a fully rendered page from the server:


@startuml
participant c: Client
participant s: Server

c -> s: GET '/'
activate s
s --> c: Main page (fully rendered)
deactivate s
c -> s: GET /current_news
activate s
s --> c: Current news page
deactivate s
@enduml
Aspect SPA SSR
Server compute Low High
Initial load time Slow (JS must download first) Fast (pre-rendered HTML)
User interaction Fast (client-side) Slower (server round-trip)
SEO Harder Better

Task

The game is almost complete — you can buy properties and build houses. But the Collect Rent button doesn’t work yet. Implement collectRent in App.jsx so it adds totalRent to the wallet using setWallet. Notice how the game logic in monopoly-game.js already provides calculateTotalRent — your App.jsx only needs to call setWallet.

Starter files
step5/styles.js
// Color group palette — maps abstract group names to banner colors
const colorGroups = {
  "ucla-blue":  "#2774AE",
  "ucla-gold":  "#FFD100",
};

function getHeaderStyle(colorGroup) {
  return {
    height: "40px",
    background: colorGroups[colorGroup] || "#ccc",
  };
}

const cardStyle = {
  width: "180px",
  border: "3px solid #333",
  borderRadius: "12px",
  overflow: "hidden",
  margin: "8px",
  fontFamily: "'Georgia', serif",
  background: "#F5F0E1",
  color: "#333",
  boxShadow: "2px 4px 10px rgba(0,0,0,0.2)",
};

const bodyStyle = {
  padding: "10px",
  textAlign: "center",
  fontSize: "14px",
};

const buttonStyle = {
  padding: "5px 12px",
  border: "2px solid #333",
  borderRadius: "6px",
  background: "#4CAF50",
  color: "white",
  fontWeight: "bold",
  cursor: "pointer",
  marginTop: "6px",
  fontSize: "12px",
};

const HOUSE_EMOJI = "\uD83C\uDFE0";
const HOTEL_EMOJI = "\uD83C\uDFE8";
const HOUSE_COST = 50;

const walletBarStyle = {
  display: "flex",
  gap: "16px",
  alignItems: "center",
  background: "#F5F0E1",
  color: "#333",
  padding: "10px 16px",
  borderRadius: "8px",
  marginBottom: "16px",
  border: "2px solid #333",
  flexWrap: "wrap",
  fontFamily: "'Georgia', serif",
};
step5/monopoly-game.js
// ── Application Layer ─────────────────────────────────────
// Pure game logic — no React, no JSX, no DOM.
// This module can be unit-tested without rendering any UI.

const MAX_HOUSES = 4;

const INITIAL_PROPERTIES = [
  { name: "Royce Hall",       price: 400, street: "Westwood Plaza", rent: 50 },
  { name: "Powell Library",   price: 350, street: "Westwood Plaza", rent: 35 },
  { name: "Kerckhoff Hall",   price: 200, street: "Bruin Walk",    rent: 25 },
  { name: "Janss Steps",      price: 150, street: "Bruin Walk",    rent: 20 },
  { name: "Bruin Bear",       price: 100, street: "Westwood Plaza", rent: 10 },
  { name: "Sculpture Garden", price: 60,  street: "Bruin Walk",    rent: 6 },
];

// Create the initial game state from the property definitions
function createInitialGameState() {
  return INITIAL_PROPERTIES.map(p => ({ ...p, owned: false, houses: 0 }));
}

// Game action: buy a property.
// Returns updated { properties, wallet } or null if invalid.
function handleBuy(properties, wallet, index) {
  const prop = properties[index];
  if (!prop.owned && wallet >= prop.price) {
    const updated = [...properties];
    updated[index] = { ...prop, owned: true };
    return { properties: updated, wallet: wallet - prop.price };
  }
  return null;
}

// Game action: build a house on an owned property.
// Returns updated { properties, wallet } or null if invalid.
function handleBuildHouse(properties, wallet, index) {
  const prop = properties[index];
  if (prop.owned && prop.houses <= MAX_HOUSES && wallet >= HOUSE_COST) {
    const updated = [...properties];
    updated[index] = { ...prop, houses: prop.houses + 1 };
    return { properties: updated, wallet: wallet - HOUSE_COST };
  }
  return null;
}

// Calculate current rent for a property (base rent + house bonus)
function currentRent(property) {
  return property.rent + property.houses * 10;
}

// Is the property fully upgraded (hotel)?
function isFullyUpgraded(property) {
  return property.houses > MAX_HOUSES;
}

// Can the player afford a given cost?
function canAfford(wallet, cost) {
  return wallet >= cost;
}

// Count how many properties the player owns
function ownedCount(properties) {
  return properties.filter(p => p.owned).length;
}

// Calculate total rent from all owned properties
function calculateTotalRent(properties) {
  return properties
    .filter(p => p.owned)
    .reduce((sum, p) => sum + currentRent(p), 0);
}
step5/PropertyCard.jsx
// ── Presentation Layer ────────────────────────────────────
// Pure UI component — renders a property card from props.
// Contains no game logic; all actions come via callbacks.

// Maps street names (game data) to color groups (style data)
const streetToColorGroup = {
  "Westwood Plaza": "ucla-blue",
  "Bruin Walk":     "ucla-gold",
};

// Converts house count to visual building icons
function renderBuildings(houses) {
  if (houses === 0) return "No buildings";
  if (houses > MAX_HOUSES) return HOTEL_EMOJI;
  return HOUSE_EMOJI.repeat(houses);
}

function PropertyCard({ property, wallet, onBuy, onBuildHouse }) {
  const { name, price, street, owned, houses } = property;
  const isMaxed = isFullyUpgraded(property);
  const rentNow = currentRent(property);
  const canBuy = canAfford(wallet, price);
  const canBuild = !isMaxed && canAfford(wallet, HOUSE_COST);

  return (
    <div style={cardStyle}>
      <div style={getHeaderStyle(streetToColorGroup[street])}></div>
      <div style={bodyStyle}>
        <h4 style={{ margin: "4px 0" }}>{name}</h4>
        <p style={{ margin: "2px 0" }}><strong>${price}</strong></p>
        {owned ? (
          <>
            <p style={{ margin: "2px 0", fontSize: "11px", color: "#666" }}>
              Rent: ${rentNow}
            </p>
            <div style={{ fontSize: "20px", minHeight: "28px" }}>
              {renderBuildings(houses)}
            </div>
            <button
              onClick={onBuildHouse}
              disabled={!canBuild}
              style={{
                ...buttonStyle,
                ...(!canBuild
                  ? { background: "#999", cursor: "default" } : {}),
              }}
            >
              {isMaxed ? "Fully Upgraded!" : "Build House ($" + HOUSE_COST + ")"}
            </button>
          </>
        ) : (
          <button
            onClick={onBuy}
            disabled={!canBuy}
            style={{
              ...buttonStyle,
              background: canBuy ? "#2774AE" : "#999",
            }}
          >
            Buy (${price})
          </button>
        )}
      </div>
    </div>
  );
}
step5/App.jsx
const { useState } = React;

// App wires the application layer (monopoly-game.js)
// to the presentation layer (PropertyCard.jsx) via React state.

function App() {
  const [wallet, setWallet] = useState(1500);
  const [properties, setProperties] = useState(createInitialGameState());

  // Apply a game action result to React state
  const applyAction = (result) => {
    if (result) {
      setWallet(result.wallet);
      setProperties(result.properties);
    }
  };

  const totalRent = calculateTotalRent(properties);

  const collectRent = () => {
    // TODO: Add totalRent to the wallet using setWallet
  };

  return (
    <div style={{ padding: "16px", fontFamily: "'Georgia', serif" }}>
      <h2 style={{ marginBottom: "4px" }}>UCLA Monopoly</h2>
      <div style={walletBarStyle}>
        <span><strong>Wallet:</strong> ${wallet}</span>
        <span><strong>Properties:</strong> {ownedCount(properties)}/{properties.length}</span>
        <span><strong>Rent/turn:</strong> ${totalRent}</span>
        <button
          onClick={collectRent}
          disabled={totalRent === 0}
          style={{
            ...buttonStyle,
            background: totalRent > 0 ? "#2774AE" : "#999",
            marginTop: 0,
          }}
        >
          Collect Rent
        </button>
      </div>
      <div style={{ display: "flex", flexWrap: "wrap" }}>
        {properties.map((prop, index) => (
          <PropertyCard
            key={index}
            property={prop}
            wallet={wallet}
            onBuy={() => applyAction(handleBuy(properties, wallet, index))}
            onBuildHouse={() => applyAction(handleBuildHouse(properties, wallet, index))}
          />
        ))}
      </div>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);