Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/client/parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export function create_child_part(
controller._unmount_callbacks.length = 0
}
}
if (!next) {
// ensure nested updates that drop this renderable tear down cached parts before stale writes
disconnect_root()
}
}
current_renderable = next
}
Expand Down Expand Up @@ -94,6 +98,10 @@ export function create_child_part(
}
}

// .render() might call invalidate something, triggering a second nested update().
// in that case, we trust that the inner update did what we needed to do
if (renderable !== current_renderable) return

// if render returned another renderable, we want to track/cache both renderables individually.
// wrap it in a nested ChildPart so that each can be tracked without ChildPart having to handle multiple renderables.
if (is_renderable(value)) value = single_part_template(value)
Expand Down
62 changes: 61 additions & 1 deletion src/client/tests/renderable.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html } from 'dhtml'
import { html, type Displayable } from 'dhtml'
import { invalidate, onMount, onUnmount } from 'dhtml/client'
import { assert_deep_eq, assert_eq, test } from '../../../scripts/test/test.ts'
import { setup } from './setup.ts'
Expand Down Expand Up @@ -527,3 +527,63 @@ test('invalidating a parent does not re-render a child', () => {
assert_eq(el.innerHTML, 'child')
assert_eq(renders, 1)
})

test('invalidating parent during child render triggers update', () => {
const { root, el } = setup()

const item = {
render() {
app.loading = true
invalidate(app)
return 'created'
},
}

const app = {
loading: false,

render() {
if (this.loading) return 'loading'
return item
},
}

root.render(app)
assert_eq(el.innerHTML, 'loading')
})

test('invalidating grandparent during child render triggers update', () => {
const { root, el } = setup()

const item = {
render() {
app.loading = true
invalidate(app)
return 'created'
},
}

const middle = {
item: null as Displayable,

render() {
return this.item
},
}

const app = {
loading: false,

render() {
if (this.loading) return 'loading'
return middle
},
}

root.render(app)
assert_eq(el.innerHTML, '')

middle.item = item
invalidate(middle)
assert_eq(el.innerHTML, 'loading')
})