fix(vocal): Stabilize event handlers with useCallback and propsRef#112
Merged
fix(vocal): Stabilize event handlers with useCallback and propsRef#112
Conversation
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.
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))
Owner
Author
|
🎉 This PR is included in version 2.0.0-beta.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
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.
Summary
_on*handlers inuseCallbackandHANDLERSinuseMemososubscribe/unsubscribealways use identical references across renderspropsRefto read current prop callbacks at call time, fixing the stale closure bug where an updatedonEnd(or similar) prop was silently ignored during an active recognition session_onEnd ↔ useTimeoutcircular dependency viaonEndRefand a stablestableTimerCbdelegateunsubscribeAllRefinline during render (not inuseEffect) so it is ready before the first event firesbabylonPrettier parser (renamed tobabelin Prettier 3)Test plan
onEndprop is called (not stale version) after a re-render during an active sessionCloses #110