-
Notifications
You must be signed in to change notification settings - Fork 7
refactor(datatrak): decompose sync status listener hooks #6578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Summary of ChangesHello @jaskfla, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request refactors the application's sync status management by transitioning from a single, event-driven state update mechanism to a more granular, hook-based approach. This change significantly enhances the modularity and reusability of sync status information, allowing components to subscribe only to the specific status updates they require, thereby simplifying component logic and improving maintainability. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit conflicted about how repetitive this file is; but on balance I think still a bit easier to code trace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request refactors the application's sync status management by introducing a new syncStatus.ts file containing several custom React hooks (e.g., useIsRequestingSync, useIsSyncing, useLastSyncTime, useSyncError). These hooks encapsulate the logic for tracking various sync states and events, allowing the SyncPage component to simplify its internal state management and event listeners. The useIsSyncing hook was moved to this new file, and date formatting for the last successful sync time was centralized within LastSyncDate.tsx. Review comments indicate that several of the new hooks suffer from stale closures due to missing dependencies in useCallback, inefficiently subscribe to all sync manager events instead of specific ones, and contain incorrect logic for clearing sync error messages.
a375546 to
1f0c5fa
Compare
4f9a81e to
9530f0a
Compare
441e203 to
88c2240
Compare
| useEffect(() => { | ||
| syncManager?.emitter?.on(SYNC_EVENT_ACTIONS.SYNC_REQUESTING, clear); | ||
| syncManager?.emitter?.on(SYNC_EVENT_ACTIONS.SYNC_IN_QUEUE, clear); | ||
| syncManager?.emitter?.on(SYNC_EVENT_ACTIONS.SYNC_STARTED, clear); | ||
| syncManager?.emitter?.on(SYNC_EVENT_ACTIONS.SYNC_ERROR, update); | ||
| return () => { | ||
| syncManager?.emitter?.off(SYNC_EVENT_ACTIONS.SYNC_REQUESTING, clear); | ||
| syncManager?.emitter?.off(SYNC_EVENT_ACTIONS.SYNC_IN_QUEUE, clear); | ||
| syncManager?.emitter?.off(SYNC_EVENT_ACTIONS.SYNC_STARTED, clear); | ||
| syncManager?.emitter?.off(SYNC_EVENT_ACTIONS.SYNC_ERROR, update); | ||
| }; | ||
| }, [clear, syncManager?.emitter, update]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to useIsSyncing (see #6574 (comment)), I’m a little spooked that the source of truth for message is actually the useState in this hook. Shouldn’t it read from syncManager every time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The entirety of this file is now included in syncStatus.ts, with one change: renamed clientSyncManager to syncManager
175f27d to
7e88fcf
Compare
| formattedLastSuccessfulSyncTime={formattedLastSuccessfulSyncTime} | ||
| lastSyncDate={syncManager.lastSuccessfulSyncTime} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- These two props are actually the same thing; one is derived from the other
- The component (which already has a heading hard-coded into it and isn’t really intended to be reused) can now just grab the last successful sync time itself from the new
useLastSyncTimehook
a5e67a8 to
ad2b028
Compare
| () => void setFormattedDate(formatLastSuccessfulSyncTime(lastSyncTime)), | ||
| 1_000, | ||
| ); | ||
| return () => void clearInterval(interval); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Missing initial value for formatted date display
The formattedDate state is initialized without a value and the useEffect only calls setFormattedDate via setInterval after the first 1-second delay. On initial render and for the first second, formattedDate will be undefined, causing the <time> element to display nothing when lastSyncTime exists. The formatted value needs to be set immediately when the component mounts or when lastSyncTime changes.
| syncManager?.emitter?.on(SYNC_EVENT_ACTIONS.SYNC_STARTED, update); | ||
| syncManager?.emitter?.on(SYNC_EVENT_ACTIONS.SYNC_STATE_CHANGED, update); | ||
| syncManager?.emitter?.on(SYNC_EVENT_ACTIONS.SYNC_ENDED, update); | ||
| return () => void syncManager?.emitter?.off('*', update); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Incorrect mitt cleanup fails to remove event listeners
In useSyncProgress, the cleanup function uses emitter?.off('*', update) but the handlers were registered on specific event types like SYNC_REQUESTING, SYNC_IN_QUEUE, etc. In mitt, calling off('*', handler) only removes a handler that was registered with on('*', handler), not handlers registered on specific event types. This cleanup doesn't actually unsubscribe the handlers, causing a memory leak and potential duplicate handler invocations.
|
|
||
| export function useSyncError(): string | null { | ||
| const syncManager = useSyncContext()?.clientSyncManager; | ||
| const [message, setMessage] = useState<string | null>(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Error message not initialized from syncManager state
The useSyncError hook initializes message state to null rather than reading from syncManager.errorMessage. The original code in SyncPage initialized with useState<string | null>(syncManager.errorMessage). If a user navigates to a page using this hook after an error occurred but before a new sync action clears it, they won't see the existing error - a behavioral regression from the previous implementation.
1936d3e to
5c3268e
Compare
9530f0a to
ba7a20d
Compare
5c3268e to
5a0befd
Compare
|
Closing in favour of #6585 |
Previously, we looked at the event type to determine what
SetStateActions needed to be firedThis PR flips that around: look at what status we care about, and listen for the relevant event(s)
Bonus: Also exposes each property through its own hook