Show Saving, Deleting, and Error States
Open:
client/src/context/BudgetContext.jsx
client/src/components/EntryForm.jsx
client/src/components/TransactionList.jsx
The initial page load already has a loading state. Mutations need their own state because a user can be viewing the rest of the Ledger while one transaction is being saved or deleted.
Add transaction mutation state:
const [savingTransaction, setSavingTransaction] = useState(false);
const [deletingTransactionId, setDeletingTransactionId] = useState(null);
Use try/finally so the state always returns to normal after the request finishes:
async function addTransaction(entry) {
setSavingTransaction(true);
try {
const created = await api.addTransaction(entry);
setTransactions((previous) => [...previous, created]);
return created;
} finally {
setSavingTransaction(false);
}
}
finally runs after both success and failure. Without it, a failed request could leave the Add button disabled forever.
Use the transaction id for deletion so the rest of the list stays available:
async function removeTransaction(id) {
setDeletingTransactionId(id);
try {
const deleted = await api.deleteTransaction(id);
setTransactions((previous) =>
previous.filter((transaction) => transaction.id !== id)
);
return deleted;
} finally {
setDeletingTransactionId(null);
}
}
The form can now communicate its state directly on the submit button:
<button type="submit" disabled={savingTransaction}>
{savingTransaction
? "saving…"
: editingTransaction
? "save"
: "add"}
</button>
The transaction list can identify the row currently being deleted:
const deleting = deletingTransactionId === transaction.id;
Use that value to disable its edit/delete controls while the request is in flight.
Errors should remain visible to the learner. Catch the failed operation in the component that knows what the user was trying to do:
try {
await removeTransaction(transaction.id);
} catch (err) {
setError(err.message || "Couldn't delete that entry");
}
The API helper should also turn connection failures and non-successful HTTP responses into Error objects. That gives the UI one predictable error shape to catch.
Test it
Use browser Network throttling to slow requests down.
Save a transaction and confirm that the button indicates saving and does not allow duplicate submissions.
Delete a transaction and confirm its actions become unavailable while the request is running.
Stop the API server and try a save or delete. The UI should show an error rather than silently doing nothing.
Checkpoint
Every transaction mutation now has a visible lifecycle:
idle → working → success
or:
idle → working → error
The learner can see what the application is doing and receives a useful message when the operation fails.