actualbudget/actual · error
Transaction ${transactionId} not found
Error message
Transaction ${transactionId} not found What it means
TransactionEdit throws when handling an edit-field action (e.g. category change) but neither the transaction with `transactionId` nor the expected `transactionToEdit` can be found in `unserializedTransactions`. It indicates the modal's action referenced a transaction that is no longer loaded in the component's state. Thrown as an invariant to prevent editing a nonexistent transaction.
Source
Thrown at packages/desktop-client/src/components/mobile/transactions/TransactionEdit.tsx:947
},
[onUpdateInner, transaction],
);
const onEditFieldInner = useCallback(
(
transactionId: TransactionEntity['id'],
name: 'category' | 'payee' | 'account' | 'date' | 'amount' | 'notes',
) => {
onRequestActiveEdit?.(getFieldName(transaction.id, name), () => {
const transactionToEdit = transactions.find(
t => t.id === transactionId,
);
const unserializedTransaction = unserializedTransactions.find(
t => t.id === transactionId,
);
if (!unserializedTransaction || !transactionToEdit) {
throw new Error(`Transaction ${transactionId} not found`);
}
switch (name) {
case 'category':
dispatch(
pushModal({
modal: {
name: 'category-autocomplete',
options: {
categoryGroups,
showHiddenCategories,
showNoneOption: true,
month: monthUtils.monthFromDate(
unserializedTransaction.date,
),
onSelect: categoryId => {
void onUpdateInner(
transactionToEdit,View on GitHub (pinned to d4334cb6e6)
Solutions
- Reload the transactions list / refetch the transaction and retry the edit.
- Confirm the transaction still exists (it may have been deleted or the id changed after a split).
- Guard the switch: if the transaction is missing, close the modal and show a 'transaction no longer exists' message instead of throwing.
- Check that the id passed into TransactionEdit matches the id in the transactions store (no id transformation).
Example fix
// before
if (!unserializedTransaction || !transactionToEdit) {
throw new Error(`Transaction ${transactionId} not found`);
}
// after
if (!unserializedTransaction || !transactionToEdit) {
dispatch(closeModal());
dispatch(addNotification({ message: t('Transaction no longer exists') }));
return;
} Defensive patterns
Strategy: validation
Validate before calling
const unserializedTransaction = unserializedTransactions.find(t => t.id === transactionId);
if (!unserializedTransaction) {
dispatch(closeModal());
dispatch(addNotification({ message: t('Transaction no longer exists') }));
return;
} Type guard
function transactionExists(id: string, txs: { id: string }[]): boolean {
return txs.some(t => t.id === id);
} Try / catch
try {
onEditField(name, value);
} catch (err) {
if (String(err).startsWith('Transaction')) {
dispatch(closeModal());
void reloadTransactions();
} else {
throw err;
}
} Prevention
- Refetch/reload transactions when the edit modal is about to act on a stale id.
- Close dependent modals whenever the underlying transaction list changes.
- Never pass ids from long-lived caches; read them from current query results.
When it happens
Trigger: A modal callback fires with a `transactionId` whose transaction is absent from the local `unserializedTransactions` array, or `transactionToEdit` is null — e.g. the transaction was deleted/split elsewhere, or the id lookup happens before transactions finish loading.
Common situations: Editing a transaction from a stale list after it was deleted on another device; opening TransactionEdit via deep link with an id that isn't in the current query results; race where a modal action fires during a state refresh.
Related errors
- Internal error: category with ID ${targetCategoryId} not fou
- Cannot delete rule: invalid id
- InitialFocus expects a single valid React element as its chi
- Unknown budget action type: ${String(type)}
- Unknown display type: ${String(type satisfies never)}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/766dd84a23da6a37.
Report an issue: GitHub.