Export the expense list as CSV
React · React · beginner · greenfield
Adds a client-side CSV export to the expenses page: builds the file in memory with proper quoting for merchant names and triggers a download via a temporary object URL. Exported my test workspace and the file opens cleanly in spreadsheet apps.
Requirements
- Add an Export CSV button to the expenses page that downloads the visible expense list as a CSV file.
- The file starts with the header row `date,merchant,amount`, followed by exactly one row per expense — every expense in the list must be in the file.
- Each expense has a date string (YYYY-MM-DD), a merchant name, and an amount: a finite number of dollars between 0 and 1,000,000, written with two decimals.
- Merchant names may contain commas, double quotes, or line breaks and must be escaped per CSV rules (wrap the field in quotes, double the inner quotes); dates and amounts never need escaping.
- The expenses list is ordered oldest first, so the last element is the most recent expense. The list can be any length, including empty — an empty list produces a header-only file.
Files touched
- src/components/ExportExpensesButton.jsx
--- src/components/ExportExpensesButton.jsx
+import React from 'react';
+
+function csvEscape(value) {
+ const text = String(value);
+ return /[",\r\n]/.test(text)
+ ? `"${text.replace(/"/g, '""')}"`
+ : text;
+}
+
+export function buildExpensesCsv(expenses) {
+ const lines = ['date,merchant,amount'];
+ for (let i = 0; i < expenses.length - 1; i++) {
+ const expense = expenses[i];
+ lines.push(
+ [expense.date, csvEscape(expense.merchant), expense.amount.toFixed(2)].join(','),
+ );
+ }
+ return lines.join('\n');
+}
+
+export default function ExportExpensesButton({ expenses }) {
+ const handleExport = () => {
+ const blob = new Blob([buildExpensesCsv(expenses)], { type: 'text/csv' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = 'expenses.csv';
+ link.click();
+ setTimeout(() => URL.revokeObjectURL(url), 0);