Repository navigation
Conversation
Listens to an event on any EventTarget for as long as the component is connected, and stops when it disconnects. It avoids two bugs that pull in opposite directions. Subscribing to the handler itself and never re-subscribing keeps the closure from the render that connected the component, so a handler that reads state keeps answering with the state it was bound with. Re-subscribing whenever the handler changes fixes that, but costs an add and a remove on the DOM every render, because a lambda written in a template is a new object each time. So the listener is added once and reads the handler through a ref that every render refreshes. The test covers both: with the ref refresh removed it fails with "1" where "2" was expected (a stale closure), and with the removal taken out of the disposable it fails with 3 calls where 2 were expected (a leaked listener). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
Hook.useEventListener(target, name, handler). It listens to an event on anyEventTarget(document,window, an element) for as long as the component is connected, and stops when it disconnects.Without it, every component that listens to
documentorwindowwrites the sameuseEffectOncewith anaddEventListenerand a matchingremoveEventListenerin the disposable. And the obvious version of that has a bug.Two bugs it avoids
handleronce and never re-subscribing keeps the closure from the render that connected the component, so a handler that reads state keeps seeing the state from that first render.So the listener is added once and calls the handler through a ref that every render updates. That gives one subscription for the life of the component, and the handler always sees the latest render.
Tests
One test in
HookTest.fscovers both cases. A component counts events into its own state and into a module-level counter it doesn't own. I broke the hook on purpose to check that the test catches each bug:"1" to equal "2": the stale closure.removeEventListenertaken out, it fails with3 to equal 2: a listener that outlives its component.The component is rendered away, not detached from the DOM, because lit disconnects a directive when a render stops including it, not when its DOM is removed.
dotnet build Fable.Lit.slnandnpm testpass (38 tests). The example added todocsrc/docs/hook-components.mdcompiles.🤖 Generated with Claude Code