mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-10-09 13:17:52 +00:00
fix: preserve timer state during debounced field saves (#7)
When editing a task field (title, description, etc.) while a timer was running, the debounced save's onSuccess handler would overwrite the timeTracking cache with stale server data. This happened because: 1. User stops timer → useStopTimer patches cache (isRunning: false) 2. Pending debounced save fires → server responds with pre-stop data 3. useUpdateTask.onSuccess writes server response → isRunning: true again The fix: in useUpdateTask.onSuccess, preserve the cached timeTracking data unless the update explicitly included timeTracking changes. Since useDebouncedSave only sends dirty fields (title, description, etc.), timeTracking is never in the update input for normal field edits, so the cache's more recent timer state is preserved. Fixes #7
This commit is contained in:
parent
bb453ab6e9
commit
bf61fd698b
1 changed files with 20 additions and 8 deletions
|
|
@ -92,16 +92,28 @@ export function useUpdateTask() {
|
|||
return useMutation({
|
||||
mutationFn: ({ id, input }: { id: string; input: UpdateTaskInput }) =>
|
||||
api.tasks.update(id, input),
|
||||
// On success, patch the cache with the server response (authoritative)
|
||||
// No optimistic update here — useDebouncedSave provides instant UI feedback
|
||||
// via localTask, and optimistic updates were causing race conditions with
|
||||
// timer start/stop mutations (cancelQueries would abort their cache patches).
|
||||
onSuccess: (task) => {
|
||||
// Patch both the list and individual caches with the real server response
|
||||
// On success, merge the server response with the current cache.
|
||||
// Preserve timeTracking from the cache if it wasn't part of this update,
|
||||
// since concurrent timer mutations (start/stop) may have already patched
|
||||
// the cache with newer time tracking state. Without this, the debounced
|
||||
// save response (which doesn't include timeTracking changes) would
|
||||
// overwrite the timer stop/start that happened in between.
|
||||
onSuccess: (serverTask, { input }) => {
|
||||
const mergeWithCachedTimeTracking = (cached: Task | undefined): Task => {
|
||||
if (!cached || input.timeTracking !== undefined) {
|
||||
// If this update explicitly included timeTracking, use server response as-is
|
||||
return serverTask;
|
||||
}
|
||||
// Preserve the cached timeTracking (which may reflect a more recent timer mutation)
|
||||
return { ...serverTask, timeTracking: cached.timeTracking };
|
||||
};
|
||||
|
||||
queryClient.setQueryData<Task[]>(['tasks'], (old) =>
|
||||
old ? old.map((t) => (t.id === task.id ? task : t)) : old
|
||||
old ? old.map((t) => (t.id === serverTask.id ? mergeWithCachedTimeTracking(t) : t)) : old
|
||||
);
|
||||
queryClient.setQueryData(['tasks', task.id], task);
|
||||
|
||||
const cachedTask = queryClient.getQueryData<Task>(['tasks', serverTask.id]);
|
||||
queryClient.setQueryData(['tasks', serverTask.id], mergeWithCachedTimeTracking(cachedTask));
|
||||
},
|
||||
// Always refetch to sync with server
|
||||
onSettled: () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue