Skip to content

Instantly share code, notes, and snippets.

@joefiorini
Last active May 4, 2018 20:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joefiorini/4372e2362e658d3092419e708416f92c to your computer and use it in GitHub Desktop.
Save joefiorini/4372e2362e658d3092419e708416f92c to your computer and use it in GitHub Desktop.
class MountableWatcher extends HTMLElement {
constructor() {
console.log("creating component");
super();
}
connectedCallback() {
this.render();
}
disconnectedCallback() {
console.log("disconnected");
this.dispatchEvent(new CustomEvent("unmounted", { bubbles: false }));
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (attrName === "onunmount" && oldVal !== newVal) {
if (newVal === null) {
this.onunmount = null;
} else {
}
}
}
static get observedAttributes() {
return ["onunmount"];
}
get onunmount() {
return this._onUnmountFn;
}
set onunmount(handler) {
if (this._onUnmountFn) {
this.removeEventListener("unmount", this._onCheckFn);
this._onUnmountFn = null;
}
if (typeof handler === "function") {
this._onUnmountFn = handler;
this.addEventListener("unmount", this._onCheckFn);
}
}
render() {
this.innerHTML = "<p>Hello from watcher</p>";
}
}
customElements.define("mountable-watcher", MountableWatcher);
// React Elm Component
var ElmComponent = React.createClass({
initialize: function(node) {
if (node === null) return;
var app = this.props.src.embed(node, this.props.flags);
if (typeof this.props.ports !== "undefined") {
this.props.ports(app.ports);
}
},
shouldComponentUpdate: function(prevProps) {
return false;
},
render: function() {
return React.createElement("div", { ref: this.initialize });
}
});
var node = document.getElementById("elm-app");
function remount(el) {
ReactDOM.unmountComponentAtNode(el);
ReactDOM.render(React.createElement(ElmComponent, { src: Elm.Main }), el);
}
document.getElementById("button").addEventListener("click", function() {
remount(node);
});
<html>
<head>
<script src="https://unpkg.com/react@15.3.1/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15.3.1/dist/react-dom.min.js"></script>
<style>
html {
background: #F7F7F7;
}
#elm-app {
color: red;
}
</style>
</head>
<body>
<div id="elm-app"></div>
<label>
Click this button and watch the handlers leak on the console. Each time the button is clicked, the React component remounts
and adds a new handler and does not clean up old ones.
<button id="button">remount</button>
</label>
<script src="/component.js"></script>
<script src="/_compile/Main.elm"></script>
</body>
</html>
module Main exposing (..)
import Html exposing (Html, program, text)
import MountableProgram as Mountable exposing (Mountable, mountableProgram)
import Time
type alias Model =
()
type Msg
= Tick Time.Time
main : Program Never (Mountable Model) (Mountable.Msg Msg)
main =
mountableProgram
{ init = init
, view = \_ -> text "Hello, I am leaking."
, update = update
, subscriptions = \_ -> Time.every (2 * Time.second) Tick
}
init : ( (), Cmd msg )
init =
() ! []
getSeconds : a -> String
getSeconds =
toString >> String.slice 0 -3
update : Msg -> a -> ( a, Cmd Msg )
update msg model =
case msg of
Tick time ->
let
_ =
Debug.log "tick" (getSeconds time)
in
model ! []
module MountableProgram exposing (Mountable, Msg, mountableProgram)
import Html exposing (Html, program)
import Html.Events exposing (on)
import Json.Decode as Decode
type alias Mountable a =
{ model : a
, isMounted : Bool
}
type Msg msg
= Unmounted
| AppMsg msg
mountableProgram :
{ init : ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, view : model -> Html msg
, subscriptions : model -> Sub msg
}
-> Program Never (Mountable model) (Msg msg)
mountableProgram stuff =
let
init =
let
( model, cmd ) =
stuff.init
in
( { model = model, isMounted = True }, Cmd.map AppMsg cmd )
update msg model =
case msg of
Unmounted ->
Debug.log "unmounted" { model | isMounted = False } ! []
AppMsg msg ->
let
( newModel, cmd ) =
stuff.update msg model.model
in
( { model = newModel, isMounted = model.isMounted }, Cmd.map AppMsg cmd )
view model =
Html.section [] [ Html.node "mountable-watcher" [ on "unmounted" (Decode.succeed Unmounted) ] [], Html.map AppMsg (stuff.view model.model) ]
subscriptions model =
Sub.batch
[ if model.isMounted then
Sub.map AppMsg (stuff.subscriptions model.model)
else
Sub.none
]
in
program
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment