Skip to content

Commit fe7cf0d

Browse files
fix(todo-fast): make hydration work correctly with FAST-HTML example (#65)
Two issues prevented the todo-fast example from working after hydration: 1. Class field initializers overwrote hydration state RenderableFASTElement calls prepare() synchronously during super(). Esbuild compiles `@observable items: TodoItemData[] = []` as `this.items = []` in the subclass constructor AFTER super() returns, silently overwriting the 3 items that prepare() extracted from the SSR shadow DOM. Fixed by removing class field initializers from @observable properties set in prepare(). 2. ObserverMap deep proxies silently swallowed immutable-style updates With `observerMap: 'all'`, each array item is wrapped with deep observable proxies. When onToggleItem used .map() to create new plain objects, instanceResolverChanged triggered deepMerge which compared proxied getter values via deepEqual and concluded nothing changed, silently dropping the state update. Fixed by mutating items in-place (item.state = 'done') so the observable setter fires directly. Other fixes: - Move todo-item loop from light DOM into shadow DOM template and query via this.shadowRoot instead of this - Always assign this.items in prepare() to guarantee a default - Add return true to onAddKeydown so the event propagates
1 parent 8b7d952 commit fe7cf0d

3 files changed

Lines changed: 18 additions & 21 deletions

File tree

examples/app/todo-fast/src/index.html

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,7 @@
2323
</head>
2424

2525
<body>
26-
<todo-app title="{{title}}">
27-
<for each="item in items">
28-
<todo-item
29-
id="{{item.id}}"
30-
title="{{item.title}}"
31-
state="{{item.state}}"
32-
></todo-item>
33-
</for>
34-
</todo-app>
26+
<todo-app title="{{title}}"></todo-app>
3527

3628
<script type="module" src="/index.js"></script>
3729
</body>

examples/app/todo-fast/src/todo-app/todo-app.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ <h1>{{title}}</h1>
1515
</div>
1616

1717
<div class="todo-list">
18-
<slot></slot>
18+
<for each="item in items">
19+
<todo-item
20+
id="{{item.id}}"
21+
title="{{item.title}}"
22+
state="{{item.state}}"
23+
></todo-item>
24+
</for>
1925
</div>
2026

2127
<div class="footer">

examples/app/todo-fast/src/todo-app/todo-app.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ interface TodoItemData {
99

1010
export class TodoApp extends RenderableFASTElement(FASTElement) {
1111
@attr title = '';
12-
@observable items: TodoItemData[] = [];
13-
@observable remainingCount = 0;
12+
@observable items!: TodoItemData[];
13+
@observable remainingCount!: number;
1414

1515
addInput!: HTMLInputElement;
1616

@@ -26,27 +26,25 @@ export class TodoApp extends RenderableFASTElement(FASTElement) {
2626
}
2727
async prepare(): Promise<void> {
2828
const items: TodoItemData[] = [];
29-
for (const el of this.querySelectorAll('todo-item')) {
29+
for (const el of this.shadowRoot!.querySelectorAll('todo-item')) {
3030
items.push({
3131
id: el.getAttribute('id') || '',
3232
title: el.getAttribute('title') || '',
3333
state: el.getAttribute('state') || 'pending',
3434
});
3535
}
36+
this.items = items;
3637
if (items.length > 0) {
37-
this.items = items;
3838
this.nextId = Math.max(...items.map(i => Number(i.id) || 0)) + 1;
3939
}
4040
this.updateCount();
4141
}
4242

4343
onToggleItem(e: CustomEvent<{id: string}>): void {
44-
const id = e.detail.id;
45-
this.items = this.items.map(item =>
46-
item.id === id
47-
? { ...item, state: item.state === 'done' ? 'pending' : 'done' }
48-
: item
49-
);
44+
const item = this.items.find(i => i.id === e.detail.id);
45+
if (item) {
46+
item.state = item.state === 'done' ? 'pending' : 'done';
47+
}
5048
this.updateCount();
5149
}
5250

@@ -55,10 +53,11 @@ export class TodoApp extends RenderableFASTElement(FASTElement) {
5553
this.updateCount();
5654
}
5755

58-
onAddKeydown(e: KeyboardEvent): void {
56+
onAddKeydown(e: KeyboardEvent) {
5957
if (e.key === 'Enter') {
6058
this.addTodo();
6159
}
60+
return true;
6261
}
6362

6463
onAddClick(): void {

0 commit comments

Comments
 (0)