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

  1. Reload the transactions list / refetch the transaction and retry the edit.
  2. Confirm the transaction still exists (it may have been deleted or the id changed after a split).
  3. Guard the switch: if the transaction is missing, close the modal and show a 'transaction no longer exists' message instead of throwing.
  4. 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

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


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/766dd84a23da6a37. Report an issue: GitHub.