Module 04: React Applications
Learning Focus: Component-based architecture, React fundamentals, state management, and modern hooks
Table of Contents
Module Overview
What We're Building
Module 04 contains 4 separate React projects, each focusing on different aspects:
- react-00: React fundamentals (components, props, state)
 - react-01: Simple "Hello World" starter
 - react-02: Complex application (Linked List + Cities manager)
 - react-03: Production-ready structure
 
Why React?
Before React (2013), building interactive UIs meant:
- Manual DOM manipulation (jQuery spaghetti)
 - Inconsistent state across components
 - Difficult to maintain as apps grew
 
React introduced:
- Component-based architecture: Reusable UI pieces
 - Declarative: Describe what you want, React handles how
 - Virtual DOM: Fast, efficient updates
 - One-way data flow: Predictable state management
 
Module Statistics
- 11 tests total across 4 projects
 - 7 bugs fixed in react-02
 - 4 test files: App.test.js, component tests
 - Key concepts: Components, props, state, hooks, lifecycle
 
Core Concepts
1. Components: The Building Blocks
What is a Component? A component is a reusable piece of UI:
// Function component (modern)
function Button({ label, onClick }) {
    return (
        <button onClick={onClick}>
            {label}
        </button>
    );
}
// Usage
<Button label="Click me!" onClick={() => alert('Clicked!')} />
Class vs Function Components:
// 2021 Style: Class Component
class Welcome extends React.Component {
    constructor(props) {
        super(props);
        this.state = { count: 0 };
    }
    
    render() {
        return <h1>Count: {this.state.count}</h1>;
    }
}
// 2025 Style: Function Component with Hooks
function Welcome() {
    const [count, setCount] = useState(0);
    return <h1>Count: {count}</h1>;
}
2. Props: Passing Data Down
// Parent passes data to child via props
function App() {
    return (
        <UserCard
            name="Brennan"
            age={25}
            email="brennan@example.com"
        />
    );
}
// Child receives data via props
function UserCard({ name, age, email }) {
    return (
        <div className="card">
            <h2>{name}</h2>
            <p>Age: {age}</p>
            <p>Email: {email}</p>
        </div>
    );
}
Props Flow:
Parent Component
    ↓ (props)
Child Component
    ↓ (props)
Grandchild Component
Props are immutable (read-only) in the child.
3. State: Managing Data
import { useState } from 'react';
function Counter() {
    // Declare state variable
    const [count, setCount] = useState(0);
    
    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                Increment
            </button>
        </div>
    );
}
State Rules:
- Never mutate state directly
 - Use setter function to update
 - Updates are asynchronous
 - State is local to component
 
4. Hooks: The Game Changer
// useState: Manage component state
const [value, setValue] = useState(initialValue);
// useEffect: Side effects (API calls, subscriptions)
useEffect(() => {
    // Runs after render
    fetchData();
    
    // Cleanup function
    return () => cleanup();
}, [dependencies]);
// useContext: Share data without props drilling
const theme = useContext(ThemeContext);
// Custom hooks: Reusable logic
function useLocalStorage(key, initialValue) {
    const [value, setValue] = useState(() => {
        return localStorage.getItem(key) || initialValue;
    });
    
    useEffect(() => {
        localStorage.setItem(key, value);
    }, [key, value]);
    
    return [value, setValue];
}
Project Walkthroughs
react-00: Fundamentals
Project Structure:
react-00/
├── src/
│   ├── components/
│   │   ├── ClassComp.js         # Class component example
│   │   ├── ClassComp.test.js
│   │   ├── FuncComp.js          # Function component
│   │   ├── FuncComp.test.js
│   │   └── POJO.js              # Plain Old JavaScript Object
│   ├── App.js                   # Main component
│   └── App.test.js
└── package.json
ClassComp.js - Class Component:
import React, { Component } from "react";
class ClassComp extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }
    increment = () => {
        this.setState({ count: this.state.count + 1 });
    }
    render() {
        return (
            <div>
                <h2>Class Component</h2>
                <p>Count: {this.state.count}</p>
                <button onClick={this.increment}>
                    Increment
                </button>
            </div>
        );
    }
}
export default ClassComp;
FuncComp.js - Function Component:
import React, { useState } from "react";
function FuncComp() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <h2>Function Component</h2>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                Increment
            </button>
        </div>
    );
}
export default FuncComp;
Why Both?
- Learn class components (legacy code)
 - Prefer function components (modern React)
 - Understand evolution of React
 
react-02: Complex Application
This is the most interesting project with real data structures:
Features:
- Doubly Linked List: Custom data structure implementation
 - City Manager: CRUD operations with API integration
 - UI Components: Complex layouts and interactions
 
LLPoJo.js - Linked List Implementation:
class Node {
    constructor(id, time, todo) {
        this.id = id;
        this.time = time;
        this.todo = todo;
        this.next = null;
        this.prev = null;
    }
}
class DoublyLinkedList {
    constructor() {
        this.count = 0;
        this.current = null;
        this.head = null;
        this.tail = null;
    }
    // Add node to end of list
    append(time, todo) {  // ⚠️ BUG WAS HERE: params were swapped
        this.count++;
        const id = "k" + this.count;
        const node = new Node(id, time, todo);
        
        if (!this.head) {
            // First node
            this.current = node;
            this.head = node;
            this.tail = node;
        } else {
            // Add to end
            node.prev = this.tail;
            this.tail.next = node;
            this.tail = node;
            this.current = node;
        }
        
        return id;
    }
    // Get current node
    get() {  // ⚠️ BUG WAS HERE: returned .todo instead of node
        if (this.head === null) {
            return "(Currently Empty)";
        }
        return this.current;  // Return full node, not just todo
    }
    // Move to next node
    nextNode() {  // ⚠️ BUG WAS HERE: returned string instead of node
        if (this.tail == null) {
            return "(Currently Empty)";
        }
        
        if (this.current && this.current !== this.tail) {
            this.current = this.current.next;
            return this.current;  // Return node, not string
        }
        
        return this.current;
    }
    // Move to previous node
    prevNode() {  // ⚠️ BUG WAS HERE: returned string instead of node
        if (this.head == null) {
            return "(Currently Empty)";
        }
        
        if (this.current && this.current !== this.head) {
            this.current = this.current.prev;
            return this.current;  // Return node, not string
        }
        
        return this.current;
    }
    // Remove node by ID
    remove(id) {  // ⚠️ BUG WAS HERE: didn't accept ID parameter
        if (id) {
            let current = this.head;
            
            while (current) {
                if (current.id === id) {
                    // Handle different cases
                    if (current === this.head && current === this.tail) {
                        // Only node
                        this.head = null;
                        this.tail = null;
                        this.current = null;
                    } else if (current === this.head) {
                        // Head node
                        this.head = this.head.next;
                        this.head.prev = null;
                        if (this.current === current) {
                            this.current = this.head;
                        }
                    } else if (current === this.tail) {
                        // Tail node
                        this.tail = this.tail.prev;
                        this.tail.next = null;
                        if (this.current === current) {
                            this.current = this.tail;
                        }
                    } else {
                        // Middle node
                        current.prev.next = current.next;
                        current.next.prev = current.prev;
                        if (this.current === current) {
                            this.current = current.next;
                        }
                    }
                    
                    this.count--;
                    return true;
                }
                current = current.next;
            }
            return false;
        }
        
        // Original remove current logic...
    }
}
export default { Node, DoublyLinkedList };
Why Linked Lists in React?
- Demonstrates data structures in real apps
 - Shows how to build custom classes for React
 - Practice complex state management
 
comunitycity.js - City Manager:
class City {
    constructor(key, name, latitude, longitude, population, hemisphere, communitySize) {
        this.name = String(name);
        this.latitude = parseFloat(latitude);
        this.longitude = parseFloat(longitude);
        this.population = Number(population);
        this.hemisphere = String(hemisphere);
        this.communitySize = String(communitySize);
        this.key = String(key);
    }
    populationSize() {  // ⚠️ BUG WAS HERE: logic error
        const cityPopulation = this.population;
        
        if (cityPopulation < 0) return "[Error! Negative Population]";
        if (cityPopulation === 0) return "Ghost Town";
        if (cityPopulation >= 1 && cityPopulation < 100) return "Hamlet";
        if (cityPopulation >= 100 && cityPopulation < 1000) return "Village";
        if (cityPopulation >= 1000 && cityPopulation < 20000) return "Town";
        if (cityPopulation >= 20000 && cityPopulation < 100000) return " Large Town";
        if (cityPopulation >= 100000) return "City";  // Fixed: was >
        
        return "[Error! Undefined]";
    }
}
Bugs Fixed
React-02 Bug Summary (7 bugs)
Bug #1: Linked List Parameter Order
// ❌ WRONG
append(todo, time) {
    const node = new Node(id, todo, time);  // Wrong order!
}
// ✅ CORRECT
append(time, todo) {
    const node = new Node(id, time, todo);  // Matches Node constructor
}
Bug #2: get() Returns Wrong Type
// ❌ WRONG: Returns string, tests expect node
get() {
    return this.current.todo;  // Just the todo property
}
// ✅ CORRECT: Return full node
get() {
    return this.current;  // The entire node object
}
Bug #3-4: nextNode/prevNode Return Types
// ❌ WRONG: Returns string when should return node
nextNode() {
    this.current = this.current.next;
    return "(Currently Empty)";  // Wrong!
}
// ✅ CORRECT: Return the node
nextNode() {
    this.current = this.current.next;
    return this.current;  // The node object
}
Bug #5: remove() Missing ID Parameter
// ❌ WRONG: No way to specify which node to remove
remove() {
    // Only removes current node
}
// ✅ CORRECT: Accept ID parameter
remove(id) {
    // Find and remove node by ID
    let current = this.head;
    while (current) {
        if (current.id === id) {
            // Remove this node
        }
        current = current.next;
    }
}
Bug #6: populationSize Logic Error
// ❌ WRONG: 100,000 falls through all conditions
if (cityPopulation > 100000) return "City";  // Excludes exactly 100k
// ✅ CORRECT: Use >= for threshold
if (cityPopulation >= 100000) return "City";  // Includes 100k
Bug #7: Test Content Mismatch
// ❌ WRONG: Tests for text that doesn't exist
expect(getByText(/learn react/i)).toBeInTheDocument();
// ✅ CORRECT: Test for actual content
expect(getByText(/Welcome to Brennan's React Applications!/i)).toBeInTheDocument();
2021 vs 2025 Evolution
Component Syntax
2021: Class Components
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = { count: 0 };
        this.increment = this.increment.bind(this);  // Binding!
    }
    
    increment() {
        this.setState({ count: this.state.count + 1 });
    }
    
    componentDidMount() {
        console.log('Mounted');
    }
    
    render() {
        return (
            <div>
                <p>{this.state.count}</p>
                <button onClick={this.increment}>+</button>
            </div>
        );
    }
}
2025: Function Components with Hooks
function Counter() {
    const [count, setCount] = useState(0);
    
    useEffect(() => {
        console.log('Mounted');
    }, []);
    
    const increment = () => setCount(count + 1);
    
    return (
        <div>
            <p>{count}</p>
            <button onClick={increment}>+</button>
        </div>
    );
}
Why the change?
- ✅ Less boilerplate
 - ✅ No 
thisconfusion - ✅ Easier to share logic (custom hooks)
 - ✅ Better TypeScript support
 
Lifecycle Methods → Hooks
2021: Lifecycle Methods
class DataFetcher extends React.Component {
    componentDidMount() {
        this.fetchData();
    }
    
    componentDidUpdate(prevProps) {
        if (this.props.id !== prevProps.id) {
            this.fetchData();
        }
    }
    
    componentWillUnmount() {
        this.cleanup();
    }
    
    fetchData() {
        // Fetch logic
    }
}
2025: useEffect Hook
function DataFetcher({ id }) {
    useEffect(() => {
        fetchData();
        
        return () => cleanup();  // Cleanup on unmount
    }, [id]);  // Re-run when id changes
    
    function fetchData() {
        // Fetch logic
    }
}
State Management
2021: this.setState
class Form extends React.Component {
    state = {
        name: '',
        email: '',
        age: 0
    };
    
    handleNameChange = (e) => {
        this.setState({ name: e.target.value });
    }
    
    handleEmailChange = (e) => {
        this.setState({ email: e.target.value });
    }
    
    // Separate handler for each field...
}
2025: Multiple useState
function Form() {
    const [name, setName] = useState('');
    const [email, setEmail] = useState('');
    const [age, setAge] = useState(0);
    
    // Or use single state object
    const [form, setForm] = useState({ name: '', email: '', age: 0 });
    
    const handleChange = (field) => (e) => {
        setForm({ ...form, [field]: e.target.value });
    };
}
Build Tools
2021: Create React App
npx create-react-app my-app
# Uses Webpack, Babel
# ~200MB node_modules
# 3-5 second dev server startup
2025: Vite
npm create vite@latest my-app -- --template react
# Uses esbuild, Rollup
# ~100MB node_modules
# < 1 second dev server startup
# Hot Module Replacement (HMR)
Key Takeaways
React Mental Model
Think of React components as functions that return UI:
function Component(props) {
    // 1. Get input (props)
    const { user } = props;
    
    // 2. Process (state, effects)
    const [likes, setLikes] = useState(0);
    
    // 3. Return output (JSX)
    return <div>{user.name} has {likes} likes</div>;
}
Best Practices
1. Component Composition
// ❌ BAD: One giant component
function App() {
    return (
        <div>
            {/* 500 lines of JSX */}
        </div>
    );
}
// ✅ GOOD: Small, focused components
function App() {
    return (
        <div>
            <Header />
            <Sidebar />
            <MainContent />
            <Footer />
        </div>
    );
}
2. Props Destructuring
// ❌ BAD: Access via props object
function Card(props) {
    return <h2>{props.title}</h2>;
}
// ✅ GOOD: Destructure in parameters
function Card({ title, description, image }) {
    return (
        <div>
            <img src={image} />
            <h2>{title}</h2>
            <p>{description}</p>
        </div>
    );
}
3. Key Prop for Lists
// ❌ BAD: No key or index as key
{items.map((item, index) => (
    <div key={index}>{item}</div>
))}
// ✅ GOOD: Unique ID as key
{items.map(item => (
    <div key={item.id}>{item.name}</div>
))}
4. Conditional Rendering
// Multiple approaches
function Component({ isLoggedIn, user }) {
    // Approach 1: Ternary
    return isLoggedIn ? <Dashboard /> : <Login />;
    
    // Approach 2: && operator
    return <div>{isLoggedIn && <Dashboard />}</div>;
    
    // Approach 3: Early return
    if (!isLoggedIn) return <Login />;
    return <Dashboard />;
}
Common Patterns
Pattern 1: Container/Presenter
// Container: Logic
function UserListContainer() {
    const [users, setUsers] = useState([]);
    
    useEffect(() => {
        fetchUsers().then(setUsers);
    }, []);
    
    return <UserListPresenter users={users} />;
}
// Presenter: UI only
function UserListPresenter({ users }) {
    return (
        <ul>
            {users.map(user => <li key={user.id}>{user.name}</li>)}
        </ul>
    );
}
Pattern 2: Custom Hooks
// Reusable logic
function useApi(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        fetch(url)
            .then(r => r.json())
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false));
    }, [url]);
    
    return { data, loading, error };
}
// Usage
function UserProfile({ userId }) {
    const { data, loading, error } = useApi(`/api/users/${userId}`);
    
    if (loading) return <Spinner />;
    if (error) return <Error message={error.message} />;
    return <Profile user={data} />;
}
Pattern 3: Compound Components
function Tabs({ children }) {
    const [activeTab, setActiveTab] = useState(0);
    
    return (
        <div>
            {React.Children.map(children, (child, index) =>
                React.cloneElement(child, {
                    isActive: index === activeTab,
                    onClick: () => setActiveTab(index)
                })
            )}
        </div>
    );
}
// Usage
<Tabs>
    <Tab title="Home">Content 1</Tab>
    <Tab title="About">Content 2</Tab>
</Tabs>
Further Learning
Practice Projects
Todo App: Classic starter project
- Add/edit/delete todos
 - Filter (all/active/completed)
 - LocalStorage persistence
 
Weather App: API integration
- Fetch weather data
 - Display current conditions
 - 5-day forecast
 - Location search
 
E-commerce Cart: State management
- Product catalog
 - Add to cart
 - Quantity updates
 - Total calculation
 
Resources
Next Module
Now that we understand modern frontend with React, let's explore the backend in Module 05: Flask API Server →
Module Status: ✅ Complete (11/11 tests passing, 3
skipped integration tests)
Key Bugs Fixed: 7 (mostly in react-02)
Time Investment: ~6 hours
Key Achievement: Understanding component-based
architecture and the evolution from class to function components!