Skip to content

fix(vocal): Stabilize event handlers with useCallback and propsRef#112

Merged
untemps merged 3 commits intobetafrom
fix/110-stabilize-event-handlers
May 7, 2026
Merged

fix(vocal): Stabilize event handlers with useCallback and propsRef#112
untemps merged 3 commits intobetafrom
fix/110-stabilize-event-handlers

Conversation

@untemps
Copy link
Copy Markdown
Owner

@untemps untemps commented May 6, 2026

Summary

  • Wraps all _on* handlers in useCallback and HANDLERS in useMemo so subscribe/unsubscribe always use identical references across renders
  • Introduces propsRef to read current prop callbacks at call time, fixing the stale closure bug where an updated onEnd (or similar) prop was silently ignored during an active recognition session
  • Breaks the _onEnd ↔ useTimeout circular dependency via onEndRef and a stable stableTimerCb delegate
  • Assigns unsubscribeAllRef inline during render (not in useEffect) so it is ready before the first event fires
  • Fixes the deprecated babylon Prettier parser (renamed to babel in Prettier 3)

Test plan

  • All 64 existing tests pass
  • New regression test: updated onEnd prop is called (not stale version) after a re-render during an active session
  • New regression test: recognition correctly ends after a re-render during an active session

Closes #110

untemps added 3 commits May 6, 2026 22:36
Wrap all _on* handlers in useCallback and HANDLERS in useMemo so
subscribe/unsubscribe always use identical references across renders.
Introduce propsRef to always read current prop callbacks at call time,
fixing the stale closure bug where an updated onEnd (or similar) prop
was silently ignored when the component re-rendered during an active
recognition session.

Break the _onEnd <-> useTimeout circular dependency via onEndRef and a
stable stableTimerCb delegate. Assign unsubscribeAllRef inline during
render (not in useEffect) so it is ready before the first event fires.

Also update .prettierrc: rename deprecated babylon parser to babel
(Prettier 3 removed the babylon alias).
…ngthen regression tests

Move unsubscribeAllRef call to a finally block in stopRecognition so
event listeners are always cleaned up even when stop() throws. Guard
the unsubscribe closure with optional chaining so partial mocks that
omit unsubscribe do not throw.

Replace the weak re-render regression test with two stronger variants:
one that asserts the component returns to idle state (cursor: pointer)
after a session ends following a re-render, and one that covers onResult
prop staleness in addition to the existing onEnd coverage.
Add regression tests for onSpeechStart, onSpeechEnd, onNoMatch, and
onError to verify that propsRef always reads the current prop at call
time after a re-render during an active session.

Extend the SpeechRecognition mock with an error() helper that fires
the error handler directly, enabling onError staleness testing without
mocking the entire useVocal hook.
@untemps untemps merged commit 69c5c00 into beta May 7, 2026
@untemps untemps deleted the fix/110-stabilize-event-handlers branch May 7, 2026 06:17
github-actions Bot pushed a commit that referenced this pull request May 7, 2026
# [2.0.0-beta.2](v2.0.0-beta.1...v2.0.0-beta.2) (2026-05-07)

### Bug Fixes

* Stabilize event handlers and fix stale prop closures ([#112](#112)) ([69c5c00](69c5c00))
@untemps
Copy link
Copy Markdown
Owner Author

untemps commented May 7, 2026

🎉 This PR is included in version 2.0.0-beta.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant