Tangy is building your next step
Shaping the lesson around what you want to make.
Tangy is building your next step
Shaping the lesson around what you want to make.
Computer Science
Turn your existing HTML, CSS, and early React experience into a small full-stack to-do app. You will build the page structure first, add a focused Express server and tiny JSON API, then connect the browser with vanilla JavaScript so tasks can be added, completed, and removed.
Follow the path module by module. The layout keeps the lesson count, your progress, and the module description in one scan.
Make the To-Do Page Work Without a Server
3 lessonsUse your existing HTML and CSS foundation to create a complete, testable to-do interface before introducing Express. The result is a useful front end with local browser behavior that can later be replaced by API calls.
Put the To-Do Data Behind Express
3 lessonsCross the bridge from familiar browser code to a small Node.js server: first run one route, then add only the API behavior the project needs. The app becomes a working full-stack foundation without adding a database or framework-heavy architecture.
Connect the Browser to the API
4 lessonsSwap local array mutations for fetch requests while keeping the interface the learner already built. Finish with a polished, runnable app that proves the browser, Express routes, and DOM are working together.
Public lesson
Your HTML and CSS work already gives you the right instincts for page structure and targeted selectors. This lesson applies those skills inside your React app: first shape the interface, then make each part easy to change later.
For now, the controls only need to look correct. We will wire up their behavior in a later lesson.
Tasks
Start your React app and open it in the browser.
Then open the component currently rendered by the app—often src/App.jsx or src/App.tsx. Identify:
Do not replace useful project setup. You are reshaping the page inside the existing app.
Predict
What will happen?
Before writing JSX, sketch this hierarchy in a comment or on paper:
page
├── header
│ ├── app title
│ └── short description
└── main
├── task form section
│ ├── label
│ ├── text input
│ └── add button
└── task list section
├── section heading
└── either
├── list of task items
└── empty-state message
page
├── header
│ ├── app title
│ └── short description
└── main
├── task form section
│ ├── label
│ ├── text input
│ └── add button
└── task list section
├── section heading
└── either
├── list of task items
└── empty-state message
For one task item, choose elements for:
Use elements for their meaning, not just their default appearance. For example, a task collection should be a list, and an action should be a button.
Use four small components:
TaskFormTaskListTaskItemAppThe components can be in one file for now. The point is to give each visible part a clear home.
Create a small task shape such as:
{
id: 1,
title: "Read React notes",
completed: false
}
{
id: 1,
title: "Read React notes",
completed: false
}
Tasks
Then fill in the missing JSX in this scaffold. Adapt the class names and existing imports to your project rather than copying them blindly.
function TaskForm() {
return (
<section className="task-section">
{/* Add a heading, a labelled text input, and a submit button.
The form does not need working submission yet. */}
</section>
);
}
function TaskItem({ task }) {
return (
<li className="task-item">
{/* Add a control for completion, the task title,
and a delete button. */}
</li>
);
}
function TaskList({ tasks }) {
if (tasks.length === 0) {
return (
<p className="empty-state">
{/* Write a useful message for a person with no tasks. */}
</p>
);
}
return (
<section className="task-section">
{/* Add the list heading and map tasks into TaskItem components. */}
</section>
);
}
export default function App() {
const tasks = [
// Add two tasks with different completed values.
];
return (
<div className="app-shell">
{/* Add the page header and main content.
Render TaskForm and TaskList here. */}
</div>
);
}
function TaskForm() {
return (
<section className="task-section">
{/* Add a heading, a labelled text input, and a submit button.
The form does not need working submission yet. */}
</section>
);
}
function TaskItem({ task }) {
return (
<li className="task-item">
{/* Add a control for completion, the task title,
and a delete button. */}
</li>
);
}
function TaskList({ tasks }) {
if (tasks.length === 0) {
return (
<p className="empty-state">
{/* Write a useful message for a person with no tasks. */}
</p>
);
}
return (
<section className="task-section">
{/* Add the list heading and map tasks into TaskItem components. */}
</section>
);
}
export default function App() {
const tasks = [
// Add two tasks with different completed values.
];
return (
<div className="app-shell">
{/* Add the page header and main content.
Render TaskForm and TaskList here. */}
</div>
);
}
Tasks
As you complete it, check these details in the browser and DevTools:
<label> connected with htmlFor and id<ul><li>tasks array as a propkey, using its idThe checkboxes and buttons do not need to change state yet. They should be present and understandable.
Tasks
Temporarily set the tasks array to an empty array.
You should see the empty-state message instead of an empty <ul>. This is an important structural difference: a person with no tasks should receive guidance, not a blank area.
Tasks
Now restore the two sample tasks. You should see:
If the empty state appears even when tasks exist, check the condition around tasks.length.
Reuse the selector habits from your HTML project. Give the page a few meaningful classes rather than styling every nested element through long descendant selectors.
Tasks
Add styles for:
.empty-state: noticeable but quieter than the main headingStart with this partial stylesheet and complete the missing values using your existing CSS knowledge.
.app-shell {
/* Set the page width, outer spacing, and readable text color. */
}
.app-header {
/* Create space below the header. */
}
.app-header h1 {
/* Make the application title prominent. */
}
.task-section {
/* Give each major section comfortable spacing. */
}
.task-form {
/* Lay out the input and add button. */
}
.task-form input {
/* Make the input easy to use and fill the available row space. */
}
.task-form button,
.task-controls button {
/* Share the important button basics. */
}
.task-list {
/* Remove list indentation and choose a vertical gap. */
}
.task-item {
/* Lay out the task text and controls as one row. */
}
.task-item.completed .task-title {
/* Show that this task is complete without hiding its text. */
}
.task-controls {
/* Keep the completion and delete controls together. */
}
.empty-state {
/* Style the no-tasks message so it is easy to notice. */
}
.app-shell {
/* Set the page width, outer spacing, and readable text color. */
}
.app-header {
/* Create space below the header. */
}
.app-header h1 {
/* Make the application title prominent. */
}
.task-section {
/* Give each major section comfortable spacing. */
}
.task-form {
/* Lay out the input and add button. */
}
.task-form input {
/* Make the input easy to use and fill the available row space. */
}
.task-form button,
.task-controls button {
/* Share the important button basics. */
}
.task-list {
/* Remove list indentation and choose a vertical gap. */
}
.task-item {
/* Lay out the task text and controls as one row. */
}
.task-item.completed .task-title {
/* Show that this task is complete without hiding its text. */
}
.task-controls {
/* Keep the completion and delete controls together. */
}
.empty-state {
/* Style the no-tasks message so it is easy to notice. */
}
Keep the selectors tied to the interface’s meaning. For example, .empty-state is more useful than a selector based on “the third paragraph.”
Tasks
Use the browser at a narrow width and a wider width. Your page should remain readable without controls colliding.
Tasks
Then inspect the rendered HTML and verify:
[]Your interface is ready for behavior when the structure looks like this:
App
├── TaskForm
└── TaskList
└── TaskItem × number of tasks
App
├── TaskForm
└── TaskList
└── TaskItem × number of tasks
The next step will give those controls real state changes.
3 modules · 10 lessons
Make the To-Do Page Work Without a Server
Put the To-Do Data Behind Express
Connect the Browser to the API
Learn by building your own version.
Remix this public project to open the workspace, follow the guided build, and let the AI mentor teach you through the work instead of doing it for you.