Syncing account progress…

Back

Advanced · 20 min

Project: an interactive expense tracker

Combine a module, asynchronous initialization, input validation, and page updates.

Starting HTML

<h2>Expense tracker</h2>
<form id="expense"><label for="amount">Amount</label><input id="amount" type="number" min="0" step="any"><button id="add" type="submit">Add expense</button></form>
<p id="status">Ready</p>
<p id="total">Total: 0.00</p>

./budget.js

export function totalExpenses(values) { return values.reduce((sum, value) => sum + value, 0); }

Separate calculation from the page

Keep the calculation in a module so it can be checked independently. The page code decides when to call it and how to display its result.

import { totalExpenses } from "./budget.js";
console.log(totalExpenses([4, 6, 3]).toFixed(2));

Output

13.00

Initialize before interaction

Await initial data before using it. In this exercise the data is a local promise; a real application may load it from a service. Validate input before updating state and rendering.

import { totalExpenses } from "./budget.js";
const expenses = await Promise.resolve([4, 6]);
document.querySelector("#total").textContent = `Total: ${totalExpenses(expenses).toFixed(2)}`;
console.log(document.querySelector("#total").textContent);

Output

Total: 10.00

Put it into practice

  1. Predict each result before running the example.
  2. Complete the coding task, then explain why your solution works.

Try it yourself

Code runs on this device. When you are signed in, drafts sync to your account.

Finish the expense tracker

Add each valid submitted amount to expenses and update the displayed total. Reject blank, nonnumeric, zero, or negative amounts. The check submits 3 and then an invalid negative amount.

Page preview

Run your code, then try the controls below. Check solution resets the page and tries the actions described in the task.

Practice