Skip to content

Commit 5709aed

Browse files
committed
Only call lit-html render if LitElement subclass implements render
- #712 Introduced a breaking behavior change for situations where `render` is unimplemented, and DOM is added before being connected to the document.
1 parent 672e457 commit 5709aed

File tree

2 files changed

+74
-34
lines changed

2 files changed

+74
-34
lines changed

src/lit-element.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ declare global {
3535

3636
export interface CSSResultArray extends Array<CSSResult|CSSResultArray> {}
3737

38+
/**
39+
* Sentinal value used to avoid calling lit-html's render function when
40+
* subclasses do not implement `render`
41+
*/
42+
const renderNotImplemented = {};
43+
3844
export class LitElement extends UpdatingElement {
3945
/**
4046
* Ensure this class is marked as `finalized` as an optimization ensuring
@@ -204,11 +210,14 @@ export class LitElement extends UpdatingElement {
204210
// before that.
205211
const templateResult = this.render();
206212
super.update(changedProperties);
207-
(this.constructor as typeof LitElement)
208-
.render(
209-
templateResult,
210-
this.renderRoot,
211-
{scopeName: this.localName, eventContext: this});
213+
// If render is not implemented by the component, don't call lit-html render
214+
if (templateResult !== renderNotImplemented) {
215+
(this.constructor as typeof LitElement)
216+
.render(
217+
templateResult,
218+
this.renderRoot,
219+
{scopeName: this.localName, eventContext: this});
220+
}
212221
// When native Shadow DOM is used but adoptedStyles are not supported,
213222
// insert styling after rendering to ensure adoptedStyles have highest
214223
// priority.
@@ -229,6 +238,6 @@ export class LitElement extends UpdatingElement {
229238
* update.
230239
*/
231240
protected render(): unknown {
232-
return undefined;
241+
return renderNotImplemented;
233242
}
234243
}

src/test/lit-element_test.ts

+59-28
Original file line numberDiff line numberDiff line change
@@ -195,39 +195,42 @@ suite('LitElement', () => {
195195
assert.equal(window['litElementVersions'].length, 1);
196196
});
197197

198-
test('event fired during rendering element can trigger an update', async () => {
199-
class E extends LitElement {
200-
connectedCallback() {
201-
super.connectedCallback();
202-
this.dispatchEvent(new CustomEvent('foo', {bubbles: true, detail: 'foo'}));
203-
}
204-
}
205-
customElements.define('x-child-61012', E);
206-
207-
class F extends LitElement {
208-
209-
static get properties() {
210-
return {foo: {type: String}};
211-
}
198+
test(
199+
'event fired during rendering element can trigger an update',
200+
async () => {
201+
class E extends LitElement {
202+
connectedCallback() {
203+
super.connectedCallback();
204+
this.dispatchEvent(
205+
new CustomEvent('foo', {bubbles: true, detail: 'foo'}));
206+
}
207+
}
208+
customElements.define('x-child-61012', E);
212209

213-
foo = '';
210+
class F extends LitElement {
211+
static get properties() {
212+
return {foo: {type: String}};
213+
}
214214

215-
render() {
216-
return html`<x-child-61012 @foo=${this._handleFoo}></x-child-61012><span>${this.foo}</span>`;
217-
}
215+
foo = '';
218216

219-
_handleFoo(e: CustomEvent) {
220-
this.foo = e.detail;
221-
}
217+
render() {
218+
return html`<x-child-61012 @foo=${
219+
this._handleFoo}></x-child-61012><span>${this.foo}</span>`;
220+
}
222221

223-
}
222+
_handleFoo(e: CustomEvent) {
223+
this.foo = e.detail;
224+
}
225+
}
224226

225-
customElements.define(generateElementName(), F);
226-
const el = new F();
227-
container.appendChild(el);
228-
while (!(await el.updateComplete)) {}
229-
assert.equal(el.shadowRoot!.textContent, 'foo');
230-
});
227+
customElements.define(generateElementName(), F);
228+
const el = new F();
229+
container.appendChild(el);
230+
while (!(await el.updateComplete)) {
231+
}
232+
assert.equal(el.shadowRoot!.textContent, 'foo');
233+
});
231234

232235
test(
233236
'exceptions in `render` throw but do not prevent further updates',
@@ -266,4 +269,32 @@ suite('LitElement', () => {
266269
assert.equal(a.foo, 20);
267270
assert.equal(a.shadowRoot!.textContent, '20');
268271
});
272+
273+
test(
274+
'if `render` is unimplemented, do not overwrite renderRoot', async () => {
275+
class A extends LitElement {
276+
addedDom: HTMLElement|null = null;
277+
createRenderRoot() {
278+
return this;
279+
}
280+
connectedCallback() {
281+
super.connectedCallback();
282+
this.addedDom = this.renderRoot.querySelector('div');
283+
}
284+
}
285+
customElements.define(generateElementName(), A);
286+
const a = new A();
287+
const testDom = document.createElement('div');
288+
a.appendChild(testDom);
289+
container.appendChild(a);
290+
await a.updateComplete;
291+
assert.equal(
292+
a.addedDom,
293+
testDom,
294+
'testDom should be found in connectedCallback');
295+
assert.equal(
296+
testDom.parentNode,
297+
a,
298+
'testDom should be a child of the component');
299+
});
269300
});

0 commit comments

Comments
 (0)