CodingNic

Transactions

Return Deleted Data for Undo

Transactions 24 min read

Return Deleted Data for Undo

Open:

text
server/src/routes/transactions.js
client/src/lib/api.js
client/src/context/BudgetContext.jsx
client/src/components/TransactionList.jsx

The starter DELETE route returns an empty success response. That is enough for permanent deletion, but it leaves the browser with no copy of the deleted record to restore.

Change the route so it returns the transaction that was removed:

javascript
router.delete("/:id", async (req, res) => {
  const db = await getDb();
  const index = db.transactions.findIndex(
    (transaction) => transaction.id === req.params.id
  );

  if (index === -1) {
    return res.status(404).json({ error: "Transaction not found" });
  }

  const [deleted] = db.transactions.splice(index, 1);
  await saveDb();
  res.json(deleted);
});

splice() removes one array item and returns the removed item. That returned object becomes the payload sent back to the client.

Update the client helper so it uses the normal request() function and receives that JSON object:

javascript
export const deleteTransaction = (id) =>
  request(`/transactions/${id}`, {
    method: "DELETE",
  });

In BudgetContext, remove the transaction from shared state and return the server response:

javascript
async function removeTransaction(id) {
  const deleted = await api.deleteTransaction(id);

  setTransactions((previous) =>
    previous.filter((transaction) => transaction.id !== id)
  );

  return deleted;
}

Now the list can remember the deleted object for a short time:

javascript
const [deletedTransaction, setDeletedTransaction] = useState(null);

async function handleDelete(transaction) {
  const deleted = await removeTransaction(transaction.id);
  setDeletedTransaction(deleted);
  setConfirmDeleteId(null);
}

Use an effect to clear the undo state after five seconds:

javascript
useEffect(() => {
  if (!deletedTransaction) return;

  const timer = window.setTimeout(
    () => setDeletedTransaction(null),
    5000
  );

  return () => window.clearTimeout(timer);
}, [deletedTransaction]);

The cleanup function cancels the old timer when the deleted transaction changes or the component unmounts.

Undo can use the normal create operation:

javascript
await addTransaction({
  date: deletedTransaction.date,
  description: deletedTransaction.description,
  category: deletedTransaction.category,
  amount: deletedTransaction.amount,
  type: deletedTransaction.type,
});

This implementation detail is important: undo is not an undelete endpoint. It creates a new transaction containing the same visible data. Because the normal create endpoint generates a new id, the restored record can have a different id from the original.

Test it

Delete a transaction. A message should explain that the transaction was deleted and can be undone for five seconds.

Click undo before the timer ends. The transaction should return with the same date, description, category, amount, and type.

Delete another transaction and wait longer than five seconds. The undo action should disappear.

Checkpoint

A successful delete returns the removed data, the client can hold that data briefly, and undo restores the entry through the normal create workflow.