Skip to content

fix: make hydration work correctly with FAST-HTML example#65

Merged
mohamedmansour merged 1 commit intomainfrom
fix/hydration-list-example
Mar 5, 2026
Merged

fix: make hydration work correctly with FAST-HTML example#65
mohamedmansour merged 1 commit intomainfrom
fix/hydration-list-example

Conversation

@mohamedmansour
Copy link
Copy Markdown
Contributor

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().

  1. 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

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
@mohamedmansour mohamedmansour changed the title fix(todo-fast): make hydration work correctly with FAST-HTML example fix: make hydration work correctly with FAST-HTML example Mar 5, 2026
@mohamedmansour mohamedmansour requested review from a team, akroshg and janechu March 5, 2026 05:37
Copy link
Copy Markdown
Contributor

@akroshg akroshg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mohamedmansour mohamedmansour requested a review from a team March 5, 2026 06:05
@mohamedmansour mohamedmansour merged commit fe7cf0d into main Mar 5, 2026
10 checks passed
@mohamedmansour mohamedmansour deleted the fix/hydration-list-example branch March 5, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants