CSCI 338: Fall 2025

Software Engineering

CSCI 338: Fall 2025

Project 2 > Enhanced Frontend

Now that you have a working app, let’s add the features that make it functional: displaying todos, creating new ones, and deleting them.

1. Build the READ Feature (Display Todos)

We’ll create a component that fetches and displays todos from the backend.

1.1. Create the Todos Component

Create ui/src/Todos.jsx:

import React, { useState, useEffect } from 'react';

export default function Todos({ API_URL }) {
    const [todoList, setTodoList] = useState([]);

    async function fetchTodos() {
        const response = await fetch(`${API_URL}/todos`);
        const data = await response.json();
        setTodoList(data);
    }

    useEffect(() => {
        fetchTodos();
    }, []);

    return (
        <div className="todos-container">
            <ul>
                {todoList.map((todo) => (
                    <li key={todo.id}>
                        <div>
                            {todo.completed ? (
                                <span>Completed</span>
                            ) : (
                                <span>In Progress</span>
                            )}{' '}
                            {todo.title}
                            <br />
                            {todo.description}
                        </div>
                    </li>
                ))}
            </ul>
        </div>
    );
}

What this does:

Key concepts:

1.2. Add Todos to App

Update ui/src/App.jsx:

import React from 'react';
import './globals.css';
import Todos from './Todos';  // Add this

const API_URL = 'http://localhost:8000';  // Add this

export default function App() {
    return (
        <>
            <header>
                <h1>TODO List</h1>
            </header>
            <main>
                <Todos API_URL={API_URL} />  {/* Add this */}
            </main>
        </>
    );
}

What this does:

Key concepts:

1.3. Add Styling for Todos

Add to ui/src/globals.css:

.todos-container {
    max-width: 600px;
    margin: 30px auto;
    padding: 20px;
    border: dashed 3px hotpink;
}

ul {
    list-style-type: none;
    padding: 0;
    margin: 0;
}

li {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;
    padding: 10px;
    background-color: #fff;
    border-radius: 5px;
    line-height: 1.4rem;
}

span {
    font-weight: bold;
    color: black;
    background-color: hotpink;
    padding: 2px 5px;
    border-radius: 5px;
}

Understanding what just happened:

Before you move on

  • Verify your Todos.jsx component is created and imported in App.jsx.
  • Test it now: Make sure your Docker containers are running (docker-compose up -d), then visit http://localhost:5173. You should see any existing todos displayed in the list. If you don’t have any todos yet, that’s okay - you’ll create some in the next section.

2. Build the CREATE Feature (Add New Todos)

Now we’ll add a form to create new todos.

2.1. Create the CreateTodo Component

Create ui/src/CreateTodo.jsx:

import React, { useState } from 'react';

export default function CreateTodo({ API_URL }) {
    const [title, setTitle] = useState('');
    const [description, setDescription] = useState('');

    async function handleSubmit(e) {
        e.preventDefault();

        const response = await fetch(`${API_URL}/todos`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ title, description, completed: false }),
        });

        if (response.ok) {
            setTitle('');
            setDescription('');
        }
    }

    return (
        <form className="form-container" onSubmit={handleSubmit}>
            <input
                type="text"
                placeholder="Title"
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                required
            />
            <input
                type="text"
                placeholder="Description"
                value={description}
                onChange={(e) => setDescription(e.target.value)}
            />
            <button type="submit">Create TODO</button>
        </form>
    );
}

What this does:

Key concepts:

2.2. Add CreateTodo to App

Update ui/src/App.jsx:

import React from 'react';
import './globals.css';
import Todos from './Todos';
import CreateTodo from './CreateTodo';  // Add this

const API_URL = 'http://localhost:8000';

export default function App() {
    return (
        <>
            <header>
                <h1>TODO List</h1>
            </header>
            <main>
                <Todos API_URL={API_URL} />
                <CreateTodo API_URL={API_URL} />  {/* Add this */}
            </main>
        </>
    );
}

2.3. Add Form Styling

Add to ui/src/globals.css:

.form-container {
    max-width: 600px;
    margin: 30px auto;
    padding: 20px;
    background-color: #fff;
    border-radius: 5px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.form-container input {
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    font-size: 16px;
}

.form-container button {
    padding: 10px 20px;
    background-color: hotpink;
    color: black;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 16px;
    font-weight: bold;
}

.form-container button:hover {
    background-color: #ff69b4;
}

Understanding what just happened:

Before you move on

  • Verify your CreateTodo.jsx component is created and imported in App.jsx.
  • Test it now: Visit http://localhost:5173 and try creating a new todo. Note: The new todo won’t appear in the list yet (we’ll fix this in the next section), but you should see the form and be able to submit it. Check your browser’s developer console (F12) for any errors.

3. Connect Components Together (Component Communication)

This is the key part! We need to make the Todos component refresh when a new todo is created. This teaches you how React components communicate.

3.1. Understanding the Problem

Right now:

Solution: Use “state lifting” - manage shared state in the parent (App)

3.2. Add Refresh Mechanism to App

Update ui/src/App.jsx:

import React, { useState } from 'react';  // Add useState
import './globals.css';
import Todos from './Todos';
import CreateTodo from './CreateTodo';

const API_URL = 'http://localhost:8000';

export default function App() {
    const [refreshTrigger, setRefreshTrigger] = useState(0);  // Add this

    function refreshTodos() {  // Add this function
        setRefreshTrigger((prev) => prev + 1);
    }

    return (
        <>
            <header>
                <h1>TODO List</h1>
            </header>
            <main>
                <Todos API_URL={API_URL} refreshTrigger={refreshTrigger} />  {/* Add prop */}
                <CreateTodo API_URL={API_URL} onTodoCreated={refreshTodos} />  {/* Add prop */}
            </main>
        </>
    );
}

What this does:

Key concepts:

3.3. Update Todos to Watch for Refresh

Update ui/src/Todos.jsx:

import React, { useState, useEffect } from 'react';

export default function Todos({ API_URL, refreshTrigger }) {  // Add refreshTrigger prop
    const [todoList, setTodoList] = useState([]);

    async function fetchTodos() {
        const response = await fetch(`${API_URL}/todos`);
        const data = await response.json();
        setTodoList(data);
    }

    useEffect(() => {
        fetchTodos();
    }, [refreshTrigger]);  // Add refreshTrigger to dependencies

    return (
        <div className="todos-container">
            <ul>
                {todoList.map((todo) => (
                    <li key={todo.id}>
                        <div>
                            {todo.completed ? (
                                <span>Completed</span>
                            ) : (
                                <span>In Progress</span>
                            )}{' '}
                            {todo.title}
                            <br />
                            {todo.description}
                        </div>
                    </li>
                ))}
            </ul>
        </div>
    );
}

What this does:

Key concepts:

3.4. Update CreateTodo to Call Callback

Update ui/src/CreateTodo.jsx:

import React, { useState } from 'react';

export default function CreateTodo({ API_URL, onTodoCreated }) {  // Add onTodoCreated prop
    const [title, setTitle] = useState('');
    const [description, setDescription] = useState('');

    async function handleSubmit(e) {
        e.preventDefault();

        const response = await fetch(`${API_URL}/todos`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ title, description, completed: false }),
        });

        if (response.ok && onTodoCreated) {  // Add check for callback
            onTodoCreated();  // Call the callback!
            setTitle('');
            setDescription('');
        }
    }

    return (
        <form className="form-container" onSubmit={handleSubmit}>
            <input
                type="text"
                placeholder="Title"
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                required
            />
            <input
                type="text"
                placeholder="Description"
                value={description}
                onChange={(e) => setDescription(e.target.value)}
            />
            <button type="submit">Create TODO</button>
            <div className="side-note">
                The todo list will automatically update after creating a new todo!
            </div>
        </form>
    );
}

What this does:

Understanding the complete flow:

  1. User creates todo → handleSubmit runs
  2. POST request succeeds → onTodoCreated() called
  3. This calls refreshTodos() in App
  4. refreshTrigger increments → Todos’ useEffect runs
  5. fetchTodos() called → New list fetched → UI updates!

Before you move on

  • Verify your component communication is set up correctly.
  • Test it now: Visit http://localhost:5173 and create a new todo. The new todo should automatically appear in the list immediately after you submit the form! If it doesn’t, check your browser’s developer console (F12) for errors and verify your containers are running.

4. Add DELETE Feature

Now we’ll add the ability to delete todos.

4.1. Add Delete Function to Todos

Update ui/src/Todos.jsx:

import React, { useState, useEffect } from 'react';

export default function Todos({ API_URL, refreshTrigger }) {
    const [todoList, setTodoList] = useState([]);

    async function fetchTodos() {
        const response = await fetch(`${API_URL}/todos`);
        const data = await response.json();
        setTodoList(data);
    }

    useEffect(() => {
        fetchTodos();
    }, [refreshTrigger]);

    // Add this function
    async function deleteTodo(todoId) {
        await fetch(`${API_URL}/todos/${todoId}`, {
            method: 'DELETE',
        });
        // Refresh the list to show the todo is gone
        fetchTodos();
    }

    return (
        <div className="todos-container">
            <ul>
                {todoList.map((todo) => (
                    <li key={todo.id}>
                        <div>
                            {todo.completed ? (
                                <span>Completed</span>
                            ) : (
                                <span>In Progress</span>
                            )}{' '}
                            {todo.title}
                            <br />
                            {todo.description}
                        </div>
                        {/* Add delete button */}
                        <button onClick={() => deleteTodo(todo.id)}>Delete</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

What this does:

Key concepts:

4.2. Add Button Styling

Add to ui/src/globals.css:

button {
    padding: 5px 10px;
    background-color: #ff4444;
    color: white;
    border: none;
    border-radius: 3px;
    cursor: pointer;
}

button:hover {
    background-color: #cc0000;
}

.side-note {
    font-size: 12px;
    color: #666;
    font-style: italic;
    margin-top: 10px;
}

Understanding what just happened:

Before you move on

  • Verify your delete functionality is implemented.
  • Test it now: Visit http://localhost:5173, create a test todo if needed, then click the “Delete” button. The todo should immediately disappear from the list. Test this with multiple todos to ensure it works correctly.

5. Final Testing

Now that all features are implemented, test your complete application:

  1. READ: Visit http://localhost:5173 - todos should load automatically when the page opens
  2. CREATE: Fill out the form, click “Create TODO”, verify it appears automatically in the list
  3. DELETE: Click “Delete” on a todo, verify it immediately disappears from the list

Make sure your Docker containers are running:

docker-compose up -d

If you encounter any issues, check:

Before you move on

  • Test all CRUD operations: Make sure READ, CREATE, and DELETE all work correctly in your browser.
  • Verify that your file structure looks like the one below:
project02-fall2025
├── .env
├── .git/
├── backend/
│   └── ...
├── database/
│   └── ...
└── ui/
    ├── src/
    │   ├── App.jsx          # updated
    │   ├── CreateTodo.jsx   # new
    │   ├── globals.css      # updated
    │   ├── main.jsx
    │   └── Todos.jsx        # new
    ├── Dockerfile
    ├── index.html
    ├── package.json
    └── vite.config.js

6. Summary

How It All Works Together:

The Complete Flow

  1. Page Loads:
    • main.jsx renders App
    • App renders Todos and CreateTodo
    • TodosuseEffect runs → fetchTodos() → GET request → todos displayed
  2. User Creates Todo:
    • User fills form → clicks “Create TODO”
    • CreateTodo’s handleSubmit runs → POST request → todo created
    • onTodoCreated() called → App’s refreshTodos() runs
    • refreshTrigger increments → TodosuseEffect runs → fetchTodos() → list updates
  3. User Deletes Todo:
    • User clicks “Delete” → deleteTodo() runs → DELETE request → todo removed
    • fetchTodos() called → list refreshes → todo gone

Component Communication Pattern

Key Pattern:

Key React Concepts Learned

Hooks

Component Communication

Event Handling

Data Fetching

Conditional Rendering

7. Commit and Push

Go ahead and commit / push your changes to git / GitHub.

Before you move on

Verify that all your new code is on GitHub.


← Back to Project 2 Instructions