diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@
   </a>
 </p>
 
-**Miso** is a small "[isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/)" [Haskell](https://www.haskell.org/) front-end framework for quickly building highly interactive single-page web applications. It features a virtual-dom, diffing / patching algorithm, attribute and property normalization, event delegation, event batching, SVG, Server-sent events, Websockets, type-safe [servant](https://haskell-servant.github.io/)-style routing and an extensible Subscription-based subsystem. Inspired by [Elm](http://elm-lang.org/), [Redux](http://redux.js.org/) and [Bobril](http://github.com/bobris/bobril). **Miso** is pure by default, but side effects (like `XHR`) can be introduced into the system via the `Effect` data type. **Miso** makes heavy use of the [GHCJS](https://github.com/ghcjs/ghcjs) FFI and therefore has minimal dependencies.
+**Miso** is a small "[isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/)" [Haskell](https://www.haskell.org/) front-end framework for quickly building highly interactive single-page web applications. It features a virtual-dom, diffing / patching algorithm, attribute and property normalization, event delegation, event batching, SVG, Server-sent events, Websockets, type-safe [servant](https://haskell-servant.github.io/)-style routing and an extensible Subscription-based subsystem. Inspired by [Elm](http://elm-lang.org/), [Redux](http://redux.js.org/) and [Bobril](http://github.com/bobris/bobril). **Miso** is pure by default, but side effects (like `XHR`) can be introduced into the system via the `Effect` data type. **Miso** makes heavy use of the [GHCJS](https://github.com/ghcjs/ghcjs) FFI and therefore has minimal dependencies. **Miso** can be considered a shallow [embedded domain-specific language](https://wiki.haskell.org/Embedded_domain_specific_language) for modern web programming.
 
 ## Table of Contents
 - [Quick Start](#quick-start)
diff --git a/ghc-src/Miso/Html/Internal.hs b/ghc-src/Miso/Html/Internal.hs
--- a/ghc-src/Miso/Html/Internal.hs
+++ b/ghc-src/Miso/Html/Internal.hs
@@ -40,6 +40,9 @@
   -- * Handling events
   , on
   , onWithOptions
+  -- * Life cycle events
+  , onCreated
+  , onDestroyed
   ) where
 
 import           Data.Aeson (Value(..), ToJSON(..))
@@ -219,6 +222,17 @@
    -> (r -> action)
    -> Attribute action
 onWithOptions _ _ _ _ = E ()
+
+-- | @onCreated action@ is an event that gets called after the actual DOM
+-- element is created.
+onCreated :: action -> Attribute action
+onCreated _ = E ()
+
+-- | @onDestroyed action@ is an event that gets called after the DOM element
+-- is removed from the DOM. The @action@ is given the DOM element that was
+-- removed from the DOM tree.
+onDestroyed :: action -> Attribute action
+onDestroyed _ = E ()
 
 -- | Constructs CSS for a DOM Element
 --
diff --git a/ghcjs-src/Miso/Html/Internal.hs b/ghcjs-src/Miso/Html/Internal.hs
--- a/ghcjs-src/Miso/Html/Internal.hs
+++ b/ghcjs-src/Miso/Html/Internal.hs
@@ -46,6 +46,9 @@
   -- * Handling events
   , on
   , onWithOptions
+  -- * Life cycle events
+  , onCreated
+  , onDestroyed
   -- * Events
   , defaultEvents
   -- * Subscription type
@@ -63,9 +66,10 @@
 import           GHCJS.Foreign.Callback
 import           GHCJS.Marshal
 import           GHCJS.Types
-import           JavaScript.Array.Internal  (fromList)
 import           JavaScript.Object
 import           JavaScript.Object.Internal (Object (Object))
+import qualified JavaScript.Array as JSArray
+import           JavaScript.Array.Internal (SomeJSArray(..))
 import           Servant.API
 
 import           Miso.Event.Decoder
@@ -139,7 +143,7 @@
           attr sink vnode
 
       setKids sink =
-        jsval . fromList <$>
+        jsval . JSArray.fromList <$>
           fmap (jsval . getTree) <$>
             traverse (flip runView sink) kids
 
@@ -262,6 +266,23 @@
    setProp "runEvent" cb eventHandlerObject
    setProp "options" jsOptions eventHandlerObject
    setProp eventName eo (Object eventObj)
+
+-- | @onCreated action@ is an event that gets called after the actual DOM
+-- element is created.
+onCreated :: action -> Attribute action
+onCreated action =
+  Attribute $ \sink n -> do
+    cb <- jsval <$> asyncCallback (sink action)
+    setProp "onCreated" cb n
+
+-- | @onDestroyed action@ is an event that gets called after the DOM element
+-- is removed from the DOM. The @action@ is given the DOM element that was
+-- removed from the DOM tree.
+onDestroyed :: action -> Attribute action
+onDestroyed action =
+  Attribute $ \sink n -> do
+    cb <- jsval <$> asyncCallback (sink action)
+    setProp "onDestroyed" cb n
 
 -- | @style_ attrs@ is an attribute that will set the @style@
 -- attribute of the associated DOM node to @attrs@.
diff --git a/jsbits/diff.js b/jsbits/diff.js
--- a/jsbits/diff.js
+++ b/jsbits/diff.js
@@ -2,7 +2,7 @@
 function diff (currentObj, newObj, parent) {
   if (!currentObj && !newObj) return;
   else if (!currentObj && newObj) createNode (newObj, parent);
-  else if (currentObj && !newObj) parent.removeChild (currentObj.domRef);
+  else if (currentObj && !newObj) destroyNode(currentObj, parent);
   else {
     if (currentObj.type === "vtext") {
       if (newObj.type === "vnode") replaceTextWithElement (currentObj, newObj, parent);
@@ -22,11 +22,13 @@
 function replaceElementWithText (c, n, parent) {
   n.domRef = document.createTextNode (n.text);
   parent.replaceChild (n.domRef, c.domRef);
+  callDestroyedRecursive(c);
 }
 
 function replaceTextWithElement (c, n, parent) {
   createElement (n);
   parent.replaceChild (n.domRef, c.domRef);
+  callCreated(n);
 }
 
 function populate (c, n) {
@@ -46,12 +48,14 @@
 }
 
 function diffVNodes (c, n, parent) {
-  if (c.tag === n.tag) {
+  if (c.tag === n.tag && n.key === c.key) {
     n.domRef = c.domRef;
     populate (c, n);
   } else {
     createElement(n);
     parent.replaceChild (n.domRef, c.domRef);
+    callDestroyedRecursive(c);
+    callCreated(n);
   }
 }
 
@@ -143,12 +147,34 @@
     if (obj.type === "vnode") createElement(obj);
     else obj.domRef = document.createTextNode(obj.text);
     parent.appendChild(obj.domRef);
+    callCreated(obj);
 }
 
 function createNodeDontAppend (obj) {
   if (obj.type === "vnode") createElement(obj);
   else obj.domRef = document.createTextNode(obj.text);
   return obj;
+}
+
+function destroyNode (obj, parent) {
+    parent.removeChild (obj.domRef);
+    callDestroyedRecursive(obj);
+}
+
+function callDestroyed (obj) {
+  if ("onDestroyed" in obj)
+    obj.onDestroyed();
+}
+
+function callDestroyedRecursive (obj) {
+  callDestroyed(obj);
+  for (var i in obj.children)
+    callDestroyedRecursive(obj.children[i]);
+}
+
+function callCreated (obj) {
+  if ("onCreated" in obj)
+    obj.onCreated();
 }
 
 /* Child reconciliation algorithm, inspired by kivi and Bobril */
diff --git a/jsbits/isomorphic.js b/jsbits/isomorphic.js
--- a/jsbits/isomorphic.js
+++ b/jsbits/isomorphic.js
@@ -5,6 +5,10 @@
 function walk (vtree, node) {
     var i = 0, vdomChild, domChild;
     vtree.domRef = node;
+
+    // Fire onCreated events as though the elements had just been created.
+    callCreated(vtree);
+
     while (i < vtree.children.length) {
       vdomChild = vtree.children[i];
       domChild = node.childNodes[i];
diff --git a/miso.cabal b/miso.cabal
--- a/miso.cabal
+++ b/miso.cabal
@@ -1,5 +1,5 @@
 name:                miso
-version:             0.13.0.0
+version:             0.14.0.0
 category:            Web, Miso, Data Structures
 license:             BSD3
 license-file:        LICENSE
