Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
FunctionReference,
} from "convex/server";
import type * as messages from "../messages.js";
import type * as numbers from "../numbers.js";
import type * as seed_messages from "../seed_messages.js";

/**
Expand All @@ -26,6 +27,7 @@ import type * as seed_messages from "../seed_messages.js";
*/
declare const fullApi: ApiFromModules<{
messages: typeof messages;
numbers: typeof numbers;
seed_messages: typeof seed_messages;
}>;
export declare const api: FilterApi<
Expand Down
27 changes: 27 additions & 0 deletions src/convex/numbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { v } from 'convex/values';
import { query, mutation } from './_generated/server.js';

export const get = query(async (ctx) => {
const numbers = await ctx.db.query('numbers').first();
return {
a: numbers?.a || 0,
b: numbers?.b || 0,
c: numbers?.c || 0
};
});

export const update = mutation({
args: {
a: v.number(),
b: v.number(),
c: v.number()
},
handler: async (ctx, { a, b, c }) => {
const existing = await ctx.db.query('numbers').first();
let id = existing?._id;
if (!id) {
id = await ctx.db.insert('numbers', { a: 0, b: 0, c: 0 });
}
await ctx.db.replace(id, { a, b, c });
}
});
5 changes: 5 additions & 0 deletions src/convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@ export default defineSchema({
messages: defineTable({
author: v.string(),
body: v.string()
}),
numbers: defineTable({
a: v.number(),
b: v.number(),
c: v.number()
})
});
29 changes: 29 additions & 0 deletions src/routes/inputs/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script lang="ts">
import Inputs from './Inputs.svelte';
</script>

<svelte:head>
<title>Home</title>
<meta name="description" content="Svelte demo app" />
</svelte:head>

<section>
<h1>Modifying several inputs</h1>
<p>Any user have complete control over these inputs but might change them quickly.</p>

<Inputs/>
</section>

<style>
section {
display: flex;
flex-direction: column;
align-items: center;
flex: 0.6;
}

h1 {
width: 100%;
text-align: center;
}
</style>
119 changes: 119 additions & 0 deletions src/routes/inputs/Inputs.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<script lang="ts">
import { useQuery, useConvexClient } from '$lib/client.svelte.js';
import type { Doc } from '../../convex/_generated/dataModel.js';
import { api } from '../../convex/_generated/api.js';

const convex = useConvexClient();
const serverNumbers = useQuery(api.numbers.get, {});

let numbers = $state({ a: 0, b: 0, c: 0 });
let pendingMutations = $state(0);
let lastMutationPromise: Promise<any> | null = $state(null);
let hasUnsentChanges = $state(false); // Track if we have changes waiting in debounce

// Stay in sync with server data only when no mutations are pending and there are now changes waiting to be sent
$effect(() => {
if (!serverNumbers.isLoading && serverNumbers.data &&
pendingMutations === 0 && !hasUnsentChanges) {
console.log('Received data from server');
numbers.a = serverNumbers.data.a;
numbers.b = serverNumbers.data.b;
numbers.c = serverNumbers.data.c;
}
});

// Queue updates and track pending mutations
async function queueMutation() {
if (serverNumbers.isLoading) return;

pendingMutations++;
hasUnsentChanges = false;

console.log('Updating server...', pendingMutations, 'mutations pending');
const currentMutation = convex.mutation(api.numbers.update, {
a: numbers.a,
b: numbers.b,
c: numbers.c
});

lastMutationPromise = currentMutation;

try {
await currentMutation;
console.log('saved to server');
} finally {
pendingMutations--;

// If this was the last mutation in the queue,
// explicitly sync with server state
if (pendingMutations === 0 && !hasUnsentChanges &&
serverNumbers.data && currentMutation === lastMutationPromise) {
console.log('finished persisting state to server, back to following useQuery');
numbers.a = serverNumbers.data.a;
numbers.b = serverNumbers.data.b;
numbers.c = serverNumbers.data.c;
}
}
}

// Track changes immediately but debounce the actual mutation
let updateTimeout: number | undefined;
$effect(() => {
// reference values so this is reactive on them
const currentValues = {
a: numbers.a,
b: numbers.b,
c: numbers.c
};
hasUnsentChanges = true;

clearTimeout(updateTimeout);
updateTimeout = setTimeout(queueMutation, 500) as unknown as number;

return () => clearTimeout(updateTimeout);
});
</script>

<div class="numbers">
{#if serverNumbers.isLoading}
<div>
<p>Loading values...</p>
</div>
{:else}
<div>
<label for="a">Number a:</label>
<input
id="a"
type="number"
bind:value={numbers.a}
/>
</div>

<div>
<label for="b">Number b:</label>
<input
id="b"
type="number"
bind:value={numbers.b}
/>
</div>

<div>
<label for="c">Number c:</label>
<input
id="c"
type="number"
bind:value={numbers.c}
/>
</div>

<div>
<p>Current values:</p>
<ul>
<li>a: {numbers.a}</li>
<li>b: {numbers.b}</li>
<li>c: {numbers.c}</li>
</ul>
</div>
{/if}
</div>
Loading