Skip to content

Commit daaee35

Browse files
Use sync query result access to avoid $state update in $effect.
1 parent 507e658 commit daaee35

File tree

1 file changed

+28
-14
lines changed

1 file changed

+28
-14
lines changed

src/lib/client.svelte.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { getContext, setContext, unstate } from 'svelte';
22
import { ConvexClient } from 'convex/browser';
3-
import type { FunctionReference, FunctionArgs, FunctionReturnType } from 'convex/server';
3+
import {
4+
type FunctionReference,
5+
type FunctionArgs,
6+
type FunctionReturnType,
7+
getFunctionName
8+
} from 'convex/server';
49
import { convexToJson, type Value } from 'convex/values';
10+
import { BROWSER } from 'esm-env';
511

612
const _contextKey = '$$_convexClient';
713

@@ -26,8 +32,7 @@ export const setupConvex = (url: string) => {
2632

2733
// SvelteKit provides `import { browser } from $app/environment` but this is only
2834
// accurate in application code. So use a runtime conditional instead.
29-
// https://github.com/sveltejs/kit/issues/5879
30-
const isBrowser = typeof window !== 'undefined';
35+
const isBrowser = BROWSER;
3136

3237
const client = new ConvexClient(url, { disabled: !isBrowser });
3338
setConvexClientContext(client);
@@ -70,41 +75,50 @@ export function useQuery<Query extends FunctionReference<'query'>>(
7075
// mutations on a reactive object. Is this a good idea?
7176

7277
const state: {
78+
// The
7379
result: FunctionReturnType<Query> | Error | undefined;
74-
argsForLastResult: FunctionArgs<Query>;
80+
// The last result we actually received, if this query has ever received one.
7581
lastResult: FunctionReturnType<Query> | Error | undefined;
82+
// The args (query key) of the last result that was received.
83+
argsForLastResult: FunctionArgs<Query>;
7684
} = $state({
7785
result: undefined,
7886
argsForLastResult: undefined,
7987
lastResult: undefined
8088
});
8189

82-
// When args change, unsubscribe and resubscribe.
90+
// When args change we need to unsubscribe and resubscribe.
8391
$effect(() => {
8492
const argsObject = parseArgs(args);
85-
state.result = undefined;
86-
8793
const unsubscribe = client.onUpdate(query, argsObject, (dataFromServer) => {
88-
// TODO is this helpful? (saving the original from being made reactive)
94+
// TODO is this helpful? (preventing the original from being made reactive)
8995
// (note we're potentially copying error objects here)
9096
const copy = structuredClone(dataFromServer);
9197

92-
// TODO can/should each property be frozen?
9398
state.result = copy;
9499
state.argsForLastResult = argsObject;
95100
state.lastResult = copy;
96101
});
97102
return unsubscribe;
98103
});
99104

100-
const sameArgs = $derived(
105+
// Are the args (the query key) the same as the last args we received a result for?
106+
const sameArgsAsLastResult = $derived(
101107
!!state.argsForLastResult &&
102108
JSON.stringify(convexToJson(state.argsForLastResult)) ===
103109
JSON.stringify(convexToJson(parseArgs(args)))
104110
);
105-
const useStale = $derived(!!(options.useResultFromPreviousArguments && state.lastResult));
106-
const result = $derived(useStale ? state.lastResult : state.result);
107-
const isStale = $derived(useStale && !sameArgs);
111+
const staleAllowed = $derived(!!(options.useResultFromPreviousArguments && state.lastResult));
112+
113+
// This value updates before the effect runs.
114+
const syncResult: FunctionReturnType<Query> | undefined = $derived(
115+
!client.disabled && client.client.localQueryResult(getFunctionName(query), parseArgs(args))
116+
);
117+
118+
const result = $derived(
119+
syncResult !== undefined ? syncResult : staleAllowed ? state.lastResult : undefined
120+
);
121+
const isStale = $derived(syncResult === undefined && staleAllowed && !sameArgsAsLastResult);
108122
const data = $derived.by(() => {
109123
if (result instanceof Error) {
110124
return undefined;
@@ -118,7 +132,7 @@ export function useQuery<Query extends FunctionReference<'query'>>(
118132
return undefined;
119133
});
120134

121-
// Cast to promise we're limiting ourselves to sensible values.
135+
// This TypeScript cast makes data not undefined if error and isLoading are checked first.
122136
return {
123137
get data() {
124138
return data;

0 commit comments

Comments
 (0)