CodingNic

Transactions

Show Saving, Deleting, and Error States

Transactions 20 min read

Show Saving, Deleting, and Error States

Open:

text
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:

javascript
const [savingTransaction, setSavingTransaction] = useState(false);
const [deletingTransactionId, setDeletingTransactionId] = useState(null);

Use try/finally so the state always returns to normal after the request finishes:

javascript
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:

javascript
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:

jsx
<button type="submit" disabled={savingTransaction}>
  {savingTransaction
    ? "saving…"
    : editingTransaction
      ? "save"
      : "add"}
</button>

The transaction list can identify the row currently being deleted:

javascript
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:

javascript
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:

text
idle → working → success

or:

text
idle → working → error

The learner can see what the application is doing and receives a useful message when the operation fails.