Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
TypeError: this._fragment is null
```html
<!-- App.html -->
{{#if show}}
  <Nested bind:show/>
{{else}}
  <button on:click="set({show:true})">Show it</button>
{{/if}}
<script>
  import Nested from './Nested.html';
  export default {
    components: {
      Nested
    }
  };
</script>
```

```html
<!-- Nested.html -->
<p>Nested component with button <button on:click="set({show:false})">Hide it</button></p>
```
(see [REPL](https://svelte.technology/repl?version=1.41.2&gist=5d6ba7b08348166a0ae2a71404710a29))

Now clicking "Hide it" will trigger `TypeError: this._fragment is null` in `function _set(newState)`, which is called twice and the second time `this._fragment` is `null`.



Discussion:

Resolving patch:
diff --git a/src/shared/index.js b/src/shared/index.js
--- a/src/shared/index.js
+++ b/src/shared/index.js
@@ -156,9 +156,12 @@ export function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 export function _setDev(newState) {


Test patch:
diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js
--- a/test/js/samples/collapses-text-around-comments/expected-bundle.js
+++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js
@@ -157,9 +157,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/component-static/expected-bundle.js b/test/js/samples/component-static/expected-bundle.js
--- a/test/js/samples/component-static/expected-bundle.js
+++ b/test/js/samples/component-static/expected-bundle.js
@@ -133,9 +133,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js
--- a/test/js/samples/computed-collapsed-if/expected-bundle.js
+++ b/test/js/samples/computed-collapsed-if/expected-bundle.js
@@ -133,9 +133,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js
--- a/test/js/samples/css-media-query/expected-bundle.js
+++ b/test/js/samples/css-media-query/expected-bundle.js
@@ -153,9 +153,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
--- a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
+++ b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
@@ -145,9 +145,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js
--- a/test/js/samples/each-block-changed-check/expected-bundle.js
+++ b/test/js/samples/each-block-changed-check/expected-bundle.js
@@ -165,9 +165,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js
--- a/test/js/samples/event-handlers-custom/expected-bundle.js
+++ b/test/js/samples/event-handlers-custom/expected-bundle.js
@@ -145,9 +145,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js
--- a/test/js/samples/if-block-no-update/expected-bundle.js
+++ b/test/js/samples/if-block-no-update/expected-bundle.js
@@ -149,9 +149,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js
--- a/test/js/samples/if-block-simple/expected-bundle.js
+++ b/test/js/samples/if-block-simple/expected-bundle.js
@@ -149,9 +149,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js
--- a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js
+++ b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js
@@ -149,9 +149,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/inline-style-optimized-url/expected-bundle.js b/test/js/samples/inline-style-optimized-url/expected-bundle.js
--- a/test/js/samples/inline-style-optimized-url/expected-bundle.js
+++ b/test/js/samples/inline-style-optimized-url/expected-bundle.js
@@ -149,9 +149,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/inline-style-optimized/expected-bundle.js b/test/js/samples/inline-style-optimized/expected-bundle.js
--- a/test/js/samples/inline-style-optimized/expected-bundle.js
+++ b/test/js/samples/inline-style-optimized/expected-bundle.js
@@ -149,9 +149,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/inline-style-unoptimized/expected-bundle.js b/test/js/samples/inline-style-unoptimized/expected-bundle.js
--- a/test/js/samples/inline-style-unoptimized/expected-bundle.js
+++ b/test/js/samples/inline-style-unoptimized/expected-bundle.js
@@ -149,9 +149,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/input-without-blowback-guard/expected-bundle.js b/test/js/samples/input-without-blowback-guard/expected-bundle.js
--- a/test/js/samples/input-without-blowback-guard/expected-bundle.js
+++ b/test/js/samples/input-without-blowback-guard/expected-bundle.js
@@ -153,9 +153,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/legacy-input-type/expected-bundle.js b/test/js/samples/legacy-input-type/expected-bundle.js
--- a/test/js/samples/legacy-input-type/expected-bundle.js
+++ b/test/js/samples/legacy-input-type/expected-bundle.js
@@ -151,9 +151,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/legacy-quote-class/expected-bundle.js b/test/js/samples/legacy-quote-class/expected-bundle.js
--- a/test/js/samples/legacy-quote-class/expected-bundle.js
+++ b/test/js/samples/legacy-quote-class/expected-bundle.js
@@ -168,9 +168,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/media-bindings/expected-bundle.js b/test/js/samples/media-bindings/expected-bundle.js
--- a/test/js/samples/media-bindings/expected-bundle.js
+++ b/test/js/samples/media-bindings/expected-bundle.js
@@ -161,9 +161,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js
--- a/test/js/samples/non-imported-component/expected-bundle.js
+++ b/test/js/samples/non-imported-component/expected-bundle.js
@@ -147,9 +147,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js
--- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js
+++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js
@@ -133,9 +133,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js
--- a/test/js/samples/setup-method/expected-bundle.js
+++ b/test/js/samples/setup-method/expected-bundle.js
@@ -133,9 +133,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js
--- a/test/js/samples/use-elements-as-anchors/expected-bundle.js
+++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js
@@ -157,9 +157,12 @@ function _set(newState) {
 	this._state = assign({}, oldState, newState);
 	this._recompute(changed, this._state);
 	if (this._bind) this._bind(changed, this._state);
-	dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
-	this._fragment.p(changed, this._state);
-	dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+
+	if (this._fragment) {
+		dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
+		this._fragment.p(changed, this._state);
+		dispatchObservers(this, this._observers.post, changed, this._state, oldState);
+	}
 }
 
 function callAll(fns) {
diff --git a/test/runtime/samples/component-binding-self-destroying/Nested.html b/test/runtime/samples/component-binding-self-destroying/Nested.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-binding-self-destroying/Nested.html
@@ -0,0 +1 @@
+<button on:click="set({show:false})">Hide</button>
\ No newline at end of file
diff --git a/test/runtime/samples/component-binding-self-destroying/_config.js b/test/runtime/samples/component-binding-self-destroying/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-binding-self-destroying/_config.js
@@ -0,0 +1,27 @@
+export default {
+	data: {
+		show: true
+	},
+
+	html: `
+		<button>Hide</button>
+	`,
+
+	test(assert, component, target, window) {
+		const click = new window.MouseEvent('click');
+
+		target.querySelector('button').dispatchEvent(click);
+
+		assert.equal(component.get('show'), false);
+		assert.htmlEqual(target.innerHTML, `
+			<button>Show</button>
+		`);
+
+		target.querySelector('button').dispatchEvent(click);
+
+		assert.equal(component.get('show'), true);
+		assert.htmlEqual(target.innerHTML, `
+			<button>Hide</button>
+		`);
+	}
+};
diff --git a/test/runtime/samples/component-binding-self-destroying/main.html b/test/runtime/samples/component-binding-self-destroying/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-binding-self-destroying/main.html
@@ -0,0 +1,14 @@
+{{#if show}}
+	<Nested bind:show/>
+{{else}}
+	<button on:click="set({show:true})">Show</button>
+{{/if}}
+
+<script>
+	import Nested from './Nested.html';
+	export default {
+		components: {
+			Nested
+		}
+	};
+</script>
\ No newline at end of file