graph TD A[Learn React] --> B[Build Frontend] B --> C[Connect to Node/Express] C --> D[Full-Stack Developer! ๐]
Building Interactive UIs with Modern React
2025-10-12
2-Hour Focus: Components โ State โ Real Application
What Weโre Building (2 Hours)
Hour 1: React Fundamentals (components, JSX, props, basic state)
Hour 2: Interactive Features (events, forms, data fetching, real app structure)
Using: fullstack-minimal-app
as our foundation
Goal: Build a complete React frontend ready for backend integration! ๐
Philosophy: Learn by building, AI-assisted development, practical focus
Hour 1: REACT FOUNDATIONS Hour 2: INTERACTIVE FEATURES
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Why React? โ โ Event Handling โ
โ JSX & Componentsโ โ โ Forms & Input โ
โ Props Passing โ โ API Integration โ
โ Basic State โ โ Complete App! โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
Foundation Ready Production Ready
0:00 - 1:00
Remember vanilla JavaScript from Session 2?
// Manual DOM manipulation (tedious!)
const button = document.querySelector('#loadBtn');
const output = document.querySelector('#output');
button.addEventListener('click', async () => {
output.innerHTML = 'Loading...';
const response = await fetch('/api/data');
const data = await response.json();
// Manual HTML creation (error-prone!)
output.innerHTML = '';
data.forEach(item => {
const div = document.createElement('div');
div.textContent = item.name;
output.appendChild(div);
});
});
React Makes It Elegant:
function DataDisplay() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const loadData = async () => {
setLoading(true);
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
setLoading(false);
};
return (
<div>
<button onClick={loadData}>Load Data</button>
{loading ? <p>Loading...</p> : null}
{data.map(item => <div key={item.id}>{item.name}</div>)}
</div>
);
}
React = Declarative: โHereโs what the UI should look likeโ not โHereโs how to build itโ
graph TD A[Learn React] --> B[Build Frontend] B --> C[Connect to Node/Express] C --> D[Full-Stack Developer! ๐]
Job Market: React is the #1 frontend framework
This Course: React + Node.js = Complete full-stack skills
Our Project Today: Product review aggregator frontend that will connect to Express backend next session!
Weโll use the provided minimal app as our foundation
# Clone and setup
git clone https://github.com/your-repo/fullstack-minimal-app
cd fullstack-minimal-app
# Install dependencies
npm install
# Start development (opens both frontend and backend)
npm run dev
# Frontend: http://localhost:5173
# Backend: http://localhost:4000 (we'll use next session)
What you get:
fullstack-minimal-app/
โโโ frontend/ โ React app (Vite + React)
โ โโโ src/
โ โ โโโ App.jsx โ Main component
โ โ โโโ main.jsx โ Entry point
โ โ โโโ components/ โ Our components go here
โ โโโ package.json
โโโ backend/ โ Node.js API (next session)
โโโ package.json โ Root scripts
๐ค AI Development Setup
VS Code Extensions: - GitHub Copilot - ES7+ React snippets - Tailwind CSS IntelliSense
AI Prompting Pattern:
Context: React functional component for [purpose]
Task: Create component that [specific functionality]
Constraints: Modern React hooks, Tailwind CSS
Output: Complete JSX component
A React component is a function that returns JSX
function App() {
return (
<div className="container mx-auto p-4">
<WelcomeMessage />
<WelcomeMessage />
<WelcomeMessage />
</div>
);
}
Key Concepts: - Components are reusable (use multiple times) - Components are composable (nest inside each other) - Components use JSX (looks like HTML, but itโs JavaScript)
JSX lets you write HTML-like syntax in JavaScript
function UserProfile() {
const name = "Alice Johnson";
const age = 25;
const isOnline = true;
return (
<div className="user-card">
{/* JavaScript expressions in {} */}
<h2>Welcome, {name}!</h2>
<p>Age: {age}</p>
{/* Conditional rendering */}
{isOnline && (
<span className="status online">๐ข Online</span>
)}
{/* Self-closing tags need / */}
<img src={`/avatar/${name}.jpg`} alt="Profile" />
<br />
{/* className not class */}
<button className="btn-primary">
Follow
</button>
</div>
);
}
HTML | JSX | Why? |
---|---|---|
class |
className |
class is reserved in JS |
for |
htmlFor |
for is reserved in JS |
onclick |
onClick |
camelCase convention |
style="color: red" |
style={{color: 'red'}} |
JS object syntax |
Props make components reusable by passing data into them
function ProductCard({ name, price, image, inStock }) {
return (
<div className="border rounded-lg p-4 shadow-md">
<img src={image} alt={name} className="w-full h-48 object-cover rounded" />
<h3 className="text-lg font-semibold mt-2">{name}</h3>
<p className="text-xl font-bold text-green-600">${price}</p>
{inStock ? (
<button className="w-full bg-blue-600 text-white py-2 rounded mt-2">
Add to Cart
</button>
) : (
<button className="w-full bg-gray-400 text-white py-2 rounded mt-2" disabled>
Out of Stock
</button>
)}
</div>
);
}
function ProductGrid() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<ProductCard
name="Wireless Headphones"
price={99.99}
image="/images/headphones.jpg"
inStock={true}
/>
<ProductCard
name="Bluetooth Speaker"
price={79.99}
image="/images/speaker.jpg"
inStock={false}
/>
<ProductCard
name="Smart Watch"
price={299.99}
image="/images/watch.jpg"
inStock={true}
/>
</div>
);
}
Props Pattern: Define once, use with different data!
Generate a Review Card Component
Prompt for AI:
Context: React component for displaying product reviews
Task: Create a ReviewCard component with these props:
- author (string): reviewer name
- rating (number): 1-5 stars
- title (string): review title
- content (string): review text
- date (string): review date
- source (string): "Amazon" | "BestBuy" | "Walmart"
Requirements:
- Display star rating as โ
symbols
- Show source with colored badge (Amazon=orange, BestBuy=blue, Walmart=green)
- Responsive design with Tailwind CSS
- Modern functional component syntax
Output: Complete ReviewCard.jsx component
After AI generates: 1. Create the component in frontend/src/components/ReviewCard.jsx
2. Test it in your App component 3. Try it with different prop values
State = data that can change and triggers UI updates
import { useState } from 'react';
function Counter() {
// [value, setter] = useState(initialValue)
const [count, setCount] = useState(0);
return (
<div className="text-center p-4">
<h2 className="text-2xl mb-4">Count: {count}</h2>
<div className="space-x-2">
<button
onClick={() => setCount(count - 1)}
className="px-4 py-2 bg-red-500 text-white rounded"
>
-1
</button>
<button
onClick={() => setCount(count + 1)}
className="px-4 py-2 bg-green-500 text-white rounded"
>
+1
</button>
<button
onClick={() => setCount(0)}
className="px-4 py-2 bg-gray-500 text-white rounded"
>
Reset
</button>
</div>
</div>
);
}
Key Points: - Always use the setter function (setCount
, not count = 5
) - State updates trigger re-render - Each component has its own state
function ProductDetails() {
const [showDetails, setShowDetails] = useState(false);
const [quantity, setQuantity] = useState(1);
return (
<div className="border p-4 rounded">
<h3 className="text-xl font-bold">Wireless Headphones</h3>
<p className="text-green-600 font-semibold">$99.99</p>
<button
onClick={() => setShowDetails(!showDetails)}
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
>
{showDetails ? 'Hide Details' : 'Show Details'}
</button>
{showDetails && (
<div className="mt-4 p-3 bg-gray-50 rounded">
<h4 className="font-semibold">Product Details:</h4>
<ul className="list-disc list-inside mt-2">
<li>Wireless Bluetooth 5.0</li>
<li>30-hour battery life</li>
<li>Active noise cancellation</li>
</ul>
</div>
)}
<div className="mt-4">
<label className="block text-sm font-medium">Quantity:</label>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(parseInt(e.target.value))}
min="1"
className="mt-1 border rounded px-3 py-2"
/>
</div>
</div>
);
}
Create a simple component with state:
Option 1: Like/Unlike button with counter Option 2: Show/hide product reviews section
Option 3: Color theme switcher
Use AI to generate, then test in your app!
โ Hour 1 Checkpoint
Before break, you should have: - โ Understanding of components and JSX - โ Created components with props - โ Used useState for interactive features - โ Built at least one working component
Hour 2 Preview: Forms, API calls, complete application!
1:00 - 2:00
React handles events with camelCase: onClick
, onChange
, onSubmit
function InteractiveDemo() {
const [message, setMessage] = useState('Click something!');
const [inputValue, setInputValue] = useState('');
const handleButtonClick = () => {
setMessage('Button was clicked! ๐');
};
const handleInputChange = (event) => {
setInputValue(event.target.value);
setMessage(`You typed: ${event.target.value}`);
};
const handleMouseEnter = () => {
setMessage('Mouse entered the area! ๐ญ');
};
return (
<div className="p-4 space-y-4">
<p className="text-lg font-semibold">{message}</p>
<button
onClick={handleButtonClick}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Click Me
</button>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Type something..."
className="border rounded px-3 py-2"
/>
<div
onMouseEnter={handleMouseEnter}
className="p-4 bg-yellow-100 rounded cursor-pointer"
>
Hover over me!
</div>
</div>
);
}
React controls form inputs = โControlled Componentsโ
function ProductSearch() {
const [searchTerm, setSearchTerm] = useState('');
const [category, setCategory] = useState('all');
const [priceRange, setPriceRange] = useState('any');
const [results, setResults] = useState([]);
const handleSubmit = (event) => {
event.preventDefault(); // Important: prevent page reload!
console.log('Searching for:', {
searchTerm,
category,
priceRange
});
// Simulate search results
setResults([
`Results for "${searchTerm}" in ${category}`,
`Price range: ${priceRange}`,
`Found 3 products matching your criteria`
]);
};
return (
<div className="max-w-md mx-auto p-4">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">
Search Products:
</label>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Enter product name..."
className="w-full border rounded px-3 py-2"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">
Category:
</label>
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="w-full border rounded px-3 py-2"
>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="books">Books</option>
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1">
Price Range:
</label>
<select
value={priceRange}
onChange={(e) => setPriceRange(e.target.value)}
className="w-full border rounded px-3 py-2"
>
<option value="any">Any Price</option>
<option value="under-50">Under $50</option>
<option value="50-100">$50 - $100</option>
<option value="over-100">Over $100</option>
</select>
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700"
>
Search Products
</button>
</form>
{results.length > 0 && (
<div className="mt-4 p-3 bg-green-50 rounded">
<h3 className="font-semibold">Search Results:</h3>
<ul className="mt-2">
{results.map((result, index) => (
<li key={index} className="text-sm">{result}</li>
))}
</ul>
</div>
)}
</div>
);
}
Pattern: value={state}
+ onChange={setState}
= Controlled Input
useEffect runs code after render (perfect for API calls)
import { useState, useEffect } from 'react';
function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Runs once when component mounts
useEffect(() => {
const fetchProducts = async () => {
try {
setLoading(true);
const response = await fetch('http://localhost:4000/api/products');
if (!response.ok) {
throw new Error('Failed to fetch products');
}
const data = await response.json();
setProducts(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchProducts();
}, []); // Empty dependency array = run once on mount
if (loading) {
return (
<div className="text-center p-8">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-2">Loading products...</p>
</div>
);
}
if (error) {
return (
<div className="text-center p-8 text-red-600">
<p>Error: {error}</p>
<button
onClick={() => window.location.reload()}
className="mt-2 px-4 py-2 bg-red-600 text-white rounded"
>
Try Again
</button>
</div>
);
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
{products.map(product => (
<div key={product.id} className="border rounded-lg p-4 shadow">
<h3 className="text-lg font-semibold">{product.name}</h3>
<p className="text-green-600 font-bold">${product.price}</p>
<p className="text-gray-600 text-sm mt-1">{product.description}</p>
<button className="mt-2 w-full bg-blue-600 text-white py-2 rounded">
View Details
</button>
</div>
))}
</div>
);
}
Build a Review Fetcher
Prompt for AI:
Context: React component for fetching product reviews
Task: Create ReviewFetcher component that:
1. Has a "Fetch Reviews" button
2. When clicked, calls POST /api/products/:id/fetch
3. Shows loading state while fetching
4. Displays success message with review count
5. Handles errors gracefully
6. Uses useState and async/await
Props:
- productId (string): ID of product to fetch reviews for
Requirements:
- Loading spinner during fetch
- Success/error messages
- Tailwind CSS styling
- Modern React hooks
Output: Complete React component
Test with: Mock API call (will work with real backend next session)
Letโs structure our review aggregator application
// App.jsx
import { useState } from 'react';
import ProductHeader from './components/ProductHeader';
import ReviewFetcher from './components/ReviewFetcher';
import ReviewStats from './components/ReviewStats';
import ReviewList from './components/ReviewList';
function App() {
const [reviews, setReviews] = useState([]);
const [stats, setStats] = useState(null);
const productId = "sample-product-1"; // In real app, this comes from URL
const handleReviewsFetched = (newReviews, newStats) => {
setReviews(newReviews);
setStats(newStats);
};
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto px-4 py-8">
<ProductHeader productId={productId} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8">
{/* Left Column: Actions and Stats */}
<div className="lg:col-span-1 space-y-6">
<ReviewFetcher
productId={productId}
onReviewsFetched={handleReviewsFetched}
/>
{stats && (
<ReviewStats stats={stats} />
)}
</div>
{/* Right Column: Review List */}
<div className="lg:col-span-2">
<ReviewList reviews={reviews} />
</div>
</div>
</div>
</div>
);
}
export default App;
frontend/src/
โโโ components/
โ โโโ ProductHeader.jsx โ Product info display
โ โโโ ReviewFetcher.jsx โ Fetch reviews button
โ โโโ ReviewStats.jsx โ Statistics visualization
โ โโโ ReviewList.jsx โ List of reviews
โ โโโ ReviewCard.jsx โ Individual review display
โโโ hooks/
โ โโโ useReviews.js โ Custom hook for review logic
โโโ services/
โ โโโ api.js โ API calls
โโโ App.jsx โ Main app component
function ReviewStats({ stats }) {
if (!stats) {
return (
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold mb-4">Review Statistics</h3>
<p className="text-gray-500">No statistics available yet.</p>
</div>
);
}
const { overallAverage, totalReviews, sourceBreakdown, ratingHistogram } = stats;
return (
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold mb-4">Review Statistics</h3>
{/* Overall Stats */}
<div className="text-center mb-6">
<div className="text-3xl font-bold text-blue-600">
{overallAverage.toFixed(1)}โ
</div>
<p className="text-gray-600">{totalReviews} total reviews</p>
</div>
{/* Source Breakdown */}
<div className="mb-6">
<h4 className="font-medium mb-2">By Source:</h4>
{sourceBreakdown.map(source => (
<div key={source.name} className="flex justify-between items-center py-1">
<span className="text-sm">{source.name}</span>
<span className="text-sm font-semibold">
{source.average.toFixed(1)}โ
({source.count})
</span>
</div>
))}
</div>
{/* Rating Histogram */}
<div>
<h4 className="font-medium mb-2">Rating Distribution:</h4>
{[5, 4, 3, 2, 1].map(rating => {
const count = ratingHistogram[rating] || 0;
const percentage = totalReviews > 0 ? (count / totalReviews) * 100 : 0;
return (
<div key={rating} className="flex items-center mb-1">
<span className="text-sm w-8">{rating}โ
</span>
<div className="flex-1 bg-gray-200 rounded-full h-2 mx-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${percentage}%` }}
></div>
</div>
<span className="text-sm w-8 text-right">{count}</span>
</div>
);
})}
</div>
</div>
);
}
What Youโve Learned Today:
Next Session Preview: Build the Node.js/Express backend that will power this React frontend!
Immediately after this lecture: 1-hour hands-on practice building your review aggregator frontend components using the patterns learned today.
What youโll build: - Complete React frontend for the Multi-Source Product Review Aggregator - Components for displaying and fetching reviews - Integration with mock API (preparing for real backend) - Responsive design with Tailwind CSS
This prepares you for: Next sessionโs backend development where youโll build the Express API that serves this frontend!
Key Achievements: - โก Built interactive UIs with components - ๐ Managed state and handled events - ๐ Integrated with APIs using useEffect - ๐จ Created responsive layouts - ๐ค Used AI effectively for development - ๐๏ธ Structured a real application
Next Steps: Practice building the review aggregator, then learn Node.js to create the backend! ๐
E. Bruno - React Frontend Development