diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js
index a7b23ff8..35940595 100644
--- a/src/utils/Transaction.js
+++ b/src/utils/Transaction.js
@@ -275,31 +275,30 @@ const cleanupTransactions = (transactionCleanups, i) => {
       )
       fs.push(() => {
         // deep observe events
-        transaction.changedParentTypes.forEach((events, type) =>
-          fs.push(() => {
-            // We need to think about the possibility that the user transforms the
-            // Y.Doc in the event.
-            if (type._item === null || !type._item.deleted) {
-              events = events
-                .filter(event =>
-                  event.target._item === null || !event.target._item.deleted
-                )
-              events
-                .forEach(event => {
-                  event.currentTarget = type
-                  event._path = null
-                })
-              // sort events by path length so that top-level events are fired first.
-              events
-                .sort((event1, event2) => event1.path.length - event2.path.length)
-              // We don't need to check for events.length
-              // because we know it has at least one element
-              callEventHandlerListeners(type._dEH, events, transaction)
-            }
-          })
-        )
-        fs.push(() => doc.emit('afterTransaction', [transaction, doc]))
+        transaction.changedParentTypes.forEach((events, type) => {
+          // We need to think about the possibility that the user transforms the
+          // Y.Doc in the event.
+          if (type._dEH.l.length > 0 && (type._item === null || !type._item.deleted)) {
+            events = events
+              .filter(event =>
+                event.target._item === null || !event.target._item.deleted
+              )
+            events
+              .forEach(event => {
+                event.currentTarget = type
+                // path is relative to the current target
+                event._path = null
+              })
+            // sort events by path length so that top-level events are fired first.
+            events
+              .sort((event1, event2) => event1.path.length - event2.path.length)
+            // We don't need to check for events.length
+            // because we know it has at least one element
+            callEventHandlerListeners(type._dEH, events, transaction)
+          }
+        })
       })
+      fs.push(() => doc.emit('afterTransaction', [transaction, doc]))
       callAll(fs, [])
       if (transaction._needFormattingCleanup) {
         cleanupYTextAfterTransaction(transaction)