Fetching Exchange Rates
The app adds and deletes expenses. Now add the feature that sets BudgetBuddy apart: live exchange rates from the ExchangeRate-API, so users can see their totals in any supported currency.
Implement api.js
Section titled “Implement api.js”Copy the final version from Module 06:
const BASE_URL = 'https://open.er-api.com/v6/latest';const SUPPORTED_CURRENCIES = ['USD', 'EUR', 'GBP', 'CAD', 'JPY', 'AUD', 'CHF', 'MXN'];
let cachedRates = null;let lastFetchTime = null;const CACHE_TTL = 60 * 60 * 1000; // 1 hour
export async function fetchRates(base = 'USD') { const now = Date.now(); if (cachedRates && lastFetchTime && now - lastFetchTime < CACHE_TTL) { return cachedRates; }
const response = await fetch(`${BASE_URL}/${base}`); if (!response.ok) throw new Error(`Exchange rate API returned ${response.status}`);
const data = await response.json(); if (data.result !== 'success') throw new Error(`API error: ${data['error-type']}`);
cachedRates = SUPPORTED_CURRENCIES.reduce((acc, code) => { if (data.rates[code] !== undefined) acc[code] = data.rates[code]; return acc; }, {}); lastFetchTime = now; return cachedRates;}
export function convertAmount(amount, from, to, rates) { if (from === to) return amount; const fromRate = rates[from]; const toRate = rates[to]; if (!fromRate || !toRate) throw new Error(`Unknown currency: ${from} or ${to}`); return (amount / fromRate) * toRate;}Fetch rates on startup
Section titled “Fetch rates on startup”Update main.js to fetch rates asynchronously during initialization and render with the real rates:
// main.js — updated init sectionasync function init() { showRatesLoading(true); try { rates = await fetchRates(); } catch (error) { console.warn('Exchange rates unavailable, using USD:', error.message); } finally { showRatesLoading(false); } render();}
function showRatesLoading(loading) { const select = document.querySelector('#currency-select'); select.disabled = loading;}
init();Remove the standalone render() call at the bottom of main.js — init() calls it after rates load.
The currency change handler
Section titled “The currency change handler”Wire the currency selector to update the display when the user picks a different currency:
// main.js (addition)document.querySelector('#currency-select').addEventListener('change', async event => { currentCurrency = event.target.value; render();});render() calls updateTotal(list, rates, currentCurrency) — since rates contains all supported currencies pre-fetched at startup, switching currencies is instant. No additional network request needed.
Updating updateTotal in ui.js
Section titled “Updating updateTotal in ui.js”Make sure updateTotal in ui.js converts from USD correctly:
// ui.js — updateTotal (final version)export function updateTotal(expenses, rates = { USD: 1 }, currency = 'USD') { const totalUSD = expenses.reduce((acc, e) => acc + e.amount, 0); const rate = rates[currency] ?? 1; const converted = totalUSD * rate;
const formatted = converted.toLocaleString('en-US', { style: 'currency', currency, minimumFractionDigits: currency === 'JPY' ? 0 : 2, maximumFractionDigits: currency === 'JPY' ? 0 : 2, });
document.querySelector('#total-amount').textContent = formatted; document.querySelector('#total-label').textContent = `Total (${currency}):`;}JPY has no decimal places — minimumFractionDigits: 0 handles that correctly.
The category filter handler
Section titled “The category filter handler”While you are wiring handlers, add the category filter:
// main.js (addition)document.querySelector('#category-filter').addEventListener('change', event => { currentCategory = event.target.value; render();});This updates currentCategory and calls render() — getDisplayList() already uses currentCategory to filter.
Test the full feature
Section titled “Test the full feature”With all handlers wired:
- Open the app in a local server
- Add several expenses across different categories
- Change the display currency — the total should update instantly
- Filter by category — the list should narrow and the total should update to reflect only the visible expenses
- Open DevTools → Network — confirm the exchange rate request fires once and is cached on subsequent currency changes
Exercise
Section titled “Exercise”- Implement
api.jsas shown above. - Update
main.jswith the asyncinit()function. - Add the currency change handler.
- Add the category filter handler.
- Verify all four features work together: add, delete, filter, currency conversion.
- Confirm the API call fires only once (check the Network tab).
api.jsfetches all supported currencies at once and caches the result for 1 hour.- Startup fetches rates asynchronously — the UI renders with a USD fallback if the request fails.
- Switching currencies uses the already-cached rates — no additional network requests.
updateTotalconverts USD amounts using the rate for the selected currency.Intl.NumberFormat(viatoLocaleString) handles currency formatting including JPY zero-decimal.