|
1 | 1 | <script lang="ts"> |
2 | | - import { useQuery, useConvexClient } from '$lib/client.svelte.js'; |
3 | | - import type { Doc } from '../../convex/_generated/dataModel.js'; |
4 | | - import { api } from '../../convex/_generated/api.js'; |
| 2 | +import { useQuery, useConvexClient } from '$lib/client.svelte.js'; |
| 3 | +import type { Doc } from '../../convex/_generated/dataModel.js'; |
| 4 | +import { api } from '../../convex/_generated/api.js'; |
5 | 5 |
|
6 | | - const convex = useConvexClient(); |
7 | | - const serverNumbers = useQuery(api.numbers.get, {}); |
| 6 | +const convex = useConvexClient(); |
| 7 | +const serverNumbers = useQuery(api.numbers.get, {}); |
8 | 8 |
|
9 | | - let numbers = $state(serverNumbers.isLoading ? {} : { a: serverNumbers.a, b: serverNumbers.b, c: serverNumbers.c }); |
10 | | - let pendingMutations = $state(0); |
11 | | - let lastMutationPromise: Promise<any> | null = $state(null); |
12 | | - let hasUnsentChanges = $state(false); // Track if we have changes waiting in debounce |
| 9 | +let numbers = $state(null); |
| 10 | +// Have some changes not yet been sent? |
| 11 | +let hasUnsentChanges = $state(false); |
| 12 | +// Does delivered server state not yet reflect all local changes? |
| 13 | +let hasUnsavedChanges = $state(false); |
| 14 | +let mutationInFlight = $state(false); |
13 | 15 |
|
14 | | - // Stay in sync with server data only when no mutations are pending and there are now changes waiting to be sent |
15 | | - $effect(() => { |
16 | | - if (!serverNumbers.isLoading && serverNumbers.data && |
17 | | - pendingMutations === 0 && !hasUnsentChanges) { |
18 | | - console.log('Received data from server:', { |
19 | | - a: serverNumbers.data.a, |
20 | | - b: serverNumbers.data.b, |
21 | | - c: serverNumbers.data.c, |
22 | | - }); |
23 | | - numbers.a = serverNumbers.data.a; |
24 | | - numbers.b = serverNumbers.data.b; |
25 | | - numbers.c = serverNumbers.data.c; |
26 | | - } |
27 | | - }); |
| 16 | +// Initialize local state when server data first arrives |
| 17 | +$effect(() => { |
| 18 | + if (!serverNumbers.isLoading && serverNumbers.data && !numbers) { |
| 19 | + numbers = { ...serverNumbers.data }; |
| 20 | + } |
| 21 | +}); |
28 | 22 |
|
29 | | - // Queue updates and track pending mutations |
30 | | - async function queueMutation() { |
31 | | - if (serverNumbers.isLoading) return; |
| 23 | +// Update local state with server data |
| 24 | +$effect(() => { |
| 25 | + if (!hasUnsavedChanges && !serverNumbers.isLoading && serverNumbers.data) { |
| 26 | + numbers = { ...serverNumbers.data }; |
| 27 | + } |
| 28 | +}); |
32 | 29 |
|
33 | | - pendingMutations++; |
34 | | - hasUnsentChanges = false; |
| 30 | +async function publishChanges() { |
| 31 | + hasUnsentChanges = true; |
| 32 | + hasUnsavedChanges = true; |
| 33 | + if (!numbers || mutationInFlight) return; |
35 | 34 |
|
36 | | - console.log('Updating server with', numbers, pendingMutations, 'mutations pending'); |
37 | | - const currentMutation = convex.mutation(api.numbers.update, { |
38 | | - a: numbers.a, |
39 | | - b: numbers.b, |
40 | | - c: numbers.c |
41 | | - }); |
| 35 | + hasUnsentChanges = false; |
| 36 | + mutationInFlight = true |
| 37 | + await convex.mutation(api.numbers.update, numbers); |
| 38 | + mutationInFlight = false |
42 | 39 |
|
43 | | - lastMutationPromise = currentMutation; |
| 40 | + if (hasUnsentChanges) { |
| 41 | + publishChanges(); |
| 42 | + } else { |
| 43 | + hasUnsavedChanges = false; |
| 44 | + } |
| 45 | +} |
44 | 46 |
|
45 | | - try { |
46 | | - await currentMutation; |
47 | | - console.log('saved to server'); |
48 | | - } finally { |
49 | | - pendingMutations--; |
50 | | - |
51 | | - // If this was the last mutation in the queue, |
52 | | - // explicitly sync with server state |
53 | | - if (pendingMutations === 0 && !hasUnsentChanges && |
54 | | - serverNumbers.data && currentMutation === lastMutationPromise) { |
55 | | - console.log('finished persisting state to server, back to following useQuery'); |
56 | | - numbers.a = serverNumbers.data.a; |
57 | | - numbers.b = serverNumbers.data.b; |
58 | | - numbers.c = serverNumbers.data.c; |
59 | | - } |
60 | | - } |
61 | | - } |
| 47 | +function handleNumericInput(prop, e) { |
| 48 | + numbers[prop] = e.currentTarget.valueAsNumber |
| 49 | + publishChanges(); |
| 50 | +}; |
62 | 51 |
|
63 | | - // Track changes immediately but debounce the actual mutation |
64 | | - let updateTimeout: number | undefined; |
65 | | - $effect(() => { |
66 | | - if (serverNumbers.isLoading) return; |
67 | | -
|
68 | | - // reference values so this is reactive on them |
69 | | - const currentValues = { |
70 | | - a: numbers.a, |
71 | | - b: numbers.b, |
72 | | - c: numbers.c |
73 | | - }; |
74 | | - hasUnsentChanges = true; |
75 | | - |
76 | | - clearTimeout(updateTimeout); |
77 | | - updateTimeout = setTimeout(queueMutation, 500) as unknown as number; |
78 | | -
|
79 | | - return () => clearTimeout(updateTimeout); |
80 | | - }); |
81 | 52 | </script> |
82 | 53 |
|
83 | 54 | <div class="numbers"> |
84 | | - {#if serverNumbers.isLoading} |
| 55 | + {#if serverNumbers.isLoading || !numbers} |
85 | 56 | <div> |
86 | 57 | <p>Loading values...</p> |
87 | 58 | </div> |
|
91 | 62 | <input |
92 | 63 | id="a" |
93 | 64 | type="number" |
94 | | - bind:value={numbers.a} |
| 65 | + oninput={(e) => handleNumericInput('a', e)} |
| 66 | + value={numbers.a} |
95 | 67 | /> |
96 | 68 | </div> |
97 | 69 |
|
|
100 | 72 | <input |
101 | 73 | id="b" |
102 | 74 | type="number" |
103 | | - bind:value={numbers.b} |
| 75 | + oninput={(e) => handleNumericInput('b', e)} |
| 76 | + value={numbers.b} |
104 | 77 | /> |
105 | 78 | </div> |
106 | 79 |
|
|
109 | 82 | <input |
110 | 83 | id="c" |
111 | 84 | type="number" |
112 | | - bind:value={numbers.c} |
| 85 | + oninput={(e) => handleNumericInput('c', e)} |
| 86 | + value={numbers.c} |
113 | 87 | /> |
114 | 88 | </div> |
115 | 89 |
|
|
0 commit comments