Transactions and Lists
Transactions are the moment-to-moment data in ZeroBudget. Users add them when they spend money — either through the global form at the bottom or the quick-add on each category card. Both paths call the same addTransaction action.
Transaction actions in useBudget
Section titled “Transaction actions in useBudget”const addTransaction = useCallback((tx) => { setMonthData(d => ({ ...d, transactions: [ ...d.transactions, { ...tx, id: crypto.randomUUID(), date: tx.date || new Date().toISOString().slice(0, 10), }, ], }));}, [setMonthData]);
const deleteTransaction = useCallback((id) => { setMonthData(d => ({ ...d, transactions: d.transactions.filter(t => t.id !== id), }));}, [setMonthData]);Building TransactionForm
Section titled “Building TransactionForm”The global form lets users pick any category:
export default function TransactionForm() { const { categories, addTransaction } = useBudgetContext(); const [categoryId, setCategoryId] = useState(''); const [amount, setAmount] = useState(''); const [description, setDescription] = useState(''); const [date, setDate] = useState(new Date().toISOString().slice(0, 10));
function handleSubmit(e) { e.preventDefault(); if (!categoryId || !amount) return; addTransaction({ categoryId, amount: parseFloat(amount), description: description.trim(), date }); setAmount(''); setDescription(''); }
return ( <section className="transaction-form card"> <h2>Add Transaction</h2> <form onSubmit={handleSubmit}> <select value={categoryId} onChange={e => setCategoryId(e.target.value)} required> <option value="">Select category…</option> {categories.map(c => <option key={c.id} value={c.id}>{c.name}</option>)} </select> <input type="number" value={amount} onChange={e => setAmount(e.target.value)} placeholder="0.00" min="0" step="0.01" required /> <input type="text" value={description} onChange={e => setDescription(e.target.value)} placeholder="What was this for?" /> <input type="date" value={date} onChange={e => setDate(e.target.value)} /> <button type="submit" className="btn-primary">Add Transaction</button> </form> </section> );}Quick-add on CategoryCard
Section titled “Quick-add on CategoryCard”Add a quick-add form directly to CategoryCard. It pre-fills categoryId:
// Inside CategoryCard, below the progress bar:const [quickAmt, setQuickAmt] = useState('');const [quickDesc, setQuickDesc] = useState('');
function handleQuickAdd(e) { e.preventDefault(); if (!quickAmt) return; addTransaction({ categoryId: category.id, amount: parseFloat(quickAmt), description: quickDesc.trim() }); setQuickAmt(''); setQuickDesc('');}
// In the return:<form className="quick-add" onSubmit={handleQuickAdd}> <input type="number" value={quickAmt} onChange={e => setQuickAmt(e.target.value)} placeholder="Amount" min="0" step="0.01" /> <input type="text" value={quickDesc} onChange={e => setQuickDesc(e.target.value)} placeholder="Description" /> <button type="submit" className="btn-primary btn-sm">+ Add</button></form>TransactionList with collapsible toggle
Section titled “TransactionList with collapsible toggle”export default function TransactionList({ transactions, onDelete }) { if (transactions.length === 0) return null;
return ( <ul className="transaction-list"> {transactions.map(tx => ( <li key={tx.id} className="transaction-row"> <span className="tx-date">{tx.date}</span> <span className="tx-desc">{tx.description || '—'}</span> <span className="tx-amount">{Number(tx.amount).toLocaleString('en-US', { style: 'currency', currency: 'USD' })}</span> <button className="btn-icon btn-icon--danger" onClick={() => onDelete(tx.id)}>×</button> </li> ))} </ul> );}In CategoryCard, filter transactions and add the toggle:
const { transactions, deleteTransaction } = useBudgetContext();const [showTxns, setShowTxns] = useState(false);const catTransactions = transactions.filter(t => t.categoryId === category.id);
// In the return:{catTransactions.length > 0 && ( <button className="btn-link" onClick={() => setShowTxns(v => !v)}> {showTxns ? 'Hide' : 'Show'} {catTransactions.length} transaction{catTransactions.length !== 1 ? 's' : ''} </button>)}{showTxns && <TransactionList transactions={catTransactions} onDelete={deleteTransaction} />}Exercise
Section titled “Exercise”- Add
addTransactionanddeleteTransactiontouseBudgetwithuseCallback. - Build
TransactionFormand render it at the bottom ofApp.jsx. - Add the quick-add form to
CategoryCard. - Build
TransactionListand add the collapsible toggle inCategoryCard. - Add a transaction via both paths and confirm it appears in the correct category card, updates the spent amount, and updates the progress bar.
- Both transaction paths — global form and quick-add — call the same
addTransactionaction from context. TransactionListreceives pre-filtered transactions fromCategoryCard.- The collapsible toggle uses local
showTxnsstate — nothing else needs to know whether the list is expanded. - Filtering transactions by
categoryIdis derived computation — it does not need its own state.