Skip to content

Commit 696e916

Browse files
Draft of tracking inputs
1 parent 08091f4 commit 696e916

File tree

5 files changed

+182
-0
lines changed

5 files changed

+182
-0
lines changed

src/convex/_generated/api.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
FunctionReference,
1515
} from "convex/server";
1616
import type * as messages from "../messages.js";
17+
import type * as numbers from "../numbers.js";
1718
import type * as seed_messages from "../seed_messages.js";
1819

1920
/**
@@ -26,6 +27,7 @@ import type * as seed_messages from "../seed_messages.js";
2627
*/
2728
declare const fullApi: ApiFromModules<{
2829
messages: typeof messages;
30+
numbers: typeof numbers;
2931
seed_messages: typeof seed_messages;
3032
}>;
3133
export declare const api: FilterApi<

src/convex/numbers.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { v } from 'convex/values';
2+
import { query, mutation } from './_generated/server.js';
3+
4+
export const get = query(async (ctx) => {
5+
const numbers = await ctx.db.query('numbers').first();
6+
return {
7+
a: numbers?.a || 0,
8+
b: numbers?.b || 0,
9+
c: numbers?.c || 0
10+
};
11+
});
12+
13+
export const update = mutation({
14+
args: {
15+
a: v.number(),
16+
b: v.number(),
17+
c: v.number()
18+
},
19+
handler: async (ctx, { a, b, c }) => {
20+
const existing = await ctx.db.query('numbers').first();
21+
let id = existing?._id;
22+
if (!id) {
23+
id = await ctx.db.insert('numbers', { a: 0, b: 0, c: 0 });
24+
}
25+
await ctx.db.replace(id, { a, b, c });
26+
}
27+
});

src/convex/schema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@ export default defineSchema({
55
messages: defineTable({
66
author: v.string(),
77
body: v.string()
8+
}),
9+
numbers: defineTable({
10+
a: v.number(),
11+
b: v.number(),
12+
c: v.number()
813
})
914
});

src/routes/inputs/+page.svelte

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script lang="ts">
2+
import Inputs from './Inputs.svelte';
3+
</script>
4+
5+
<svelte:head>
6+
<title>Home</title>
7+
<meta name="description" content="Svelte demo app" />
8+
</svelte:head>
9+
10+
<section>
11+
<h1>Modifying several inputs</h1>
12+
<p>Any user have complete control over these inputs but might change them quickly.</p>
13+
14+
<Inputs/>
15+
</section>
16+
17+
<style>
18+
section {
19+
display: flex;
20+
flex-direction: column;
21+
align-items: center;
22+
flex: 0.6;
23+
}
24+
25+
h1 {
26+
width: 100%;
27+
text-align: center;
28+
}
29+
</style>

src/routes/inputs/Inputs.svelte

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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';
5+
6+
const convex = useConvexClient();
7+
const serverNumbers = useQuery(api.numbers.get, {});
8+
9+
let numbers = $state({ a: 0, b: 0, c: 0 });
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
13+
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+
numbers.a = serverNumbers.data.a;
20+
numbers.b = serverNumbers.data.b;
21+
numbers.c = serverNumbers.data.c;
22+
}
23+
});
24+
25+
// Queue updates and track pending mutations
26+
async function queueMutation() {
27+
if (serverNumbers.isLoading) return;
28+
29+
pendingMutations++;
30+
hasUnsentChanges = false;
31+
32+
console.log('Updating server...', pendingMutations, 'mutations pending');
33+
const currentMutation = convex.mutation(api.numbers.update, {
34+
a: numbers.a,
35+
b: numbers.b,
36+
c: numbers.c
37+
});
38+
39+
lastMutationPromise = currentMutation;
40+
41+
try {
42+
await currentMutation;
43+
console.log('saved to server');
44+
} finally {
45+
pendingMutations--;
46+
47+
// If this was the last mutation in the queue,
48+
// explicitly sync with server state
49+
if (pendingMutations === 0 && !hasUnsentChanges &&
50+
serverNumbers.data && currentMutation === lastMutationPromise) {
51+
console.log('finished persisting state to server, back to following useQuery');
52+
numbers.a = serverNumbers.data.a;
53+
numbers.b = serverNumbers.data.b;
54+
numbers.c = serverNumbers.data.c;
55+
}
56+
}
57+
}
58+
59+
// Track changes immediately but debounce the actual mutation
60+
let updateTimeout: number | undefined;
61+
$effect(() => {
62+
// reference values so this is reactive on them
63+
const currentValues = {
64+
a: numbers.a,
65+
b: numbers.b,
66+
c: numbers.c
67+
};
68+
hasUnsentChanges = true;
69+
70+
clearTimeout(updateTimeout);
71+
updateTimeout = setTimeout(queueMutation, 500) as unknown as number;
72+
73+
return () => clearTimeout(updateTimeout);
74+
});
75+
</script>
76+
77+
<div class="numbers">
78+
{#if serverNumbers.isLoading}
79+
<div>
80+
<p>Loading values...</p>
81+
</div>
82+
{:else}
83+
<div>
84+
<label for="a">Number a:</label>
85+
<input
86+
id="a"
87+
type="number"
88+
bind:value={numbers.a}
89+
/>
90+
</div>
91+
92+
<div>
93+
<label for="b">Number b:</label>
94+
<input
95+
id="b"
96+
type="number"
97+
bind:value={numbers.b}
98+
/>
99+
</div>
100+
101+
<div>
102+
<label for="c">Number c:</label>
103+
<input
104+
id="c"
105+
type="number"
106+
bind:value={numbers.c}
107+
/>
108+
</div>
109+
110+
<div>
111+
<p>Current values:</p>
112+
<ul>
113+
<li>a: {numbers.a}</li>
114+
<li>b: {numbers.b}</li>
115+
<li>c: {numbers.c}</li>
116+
</ul>
117+
</div>
118+
{/if}
119+
</div>

0 commit comments

Comments
 (0)