Skip to content

Commit 8360d41

Browse files
Add useWatchedQuerySubscription for Vue package
1 parent 7920d37 commit 8360d41

File tree

5 files changed

+122
-32
lines changed

5 files changed

+122
-32
lines changed

.changeset/nine-pens-ring.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,29 @@ const { data } = useQuery('SELECT * FROM lists WHERE name = ?', ['aname'], {
1515
}
1616
});
1717
```
18+
19+
- Added the ability to subscribe to an existing instance of a `WatchedQuery`
20+
21+
```vue
22+
<script setup>
23+
import { useWatchedQuerySubscription } from '@powersync/vue';
24+
25+
const listsQuery = powerSync
26+
.query({
27+
sql: `SELECT * FROM lists`
28+
})
29+
.differentialWatch();
30+
31+
const { data, isLoading, isFetching, error } = useWatchedQuerySubscription(listsQuery);
32+
</script>
33+
34+
<template>
35+
<div v-if="isLoading">Loading...</div>
36+
<div v-else-if="isFetching">Updating results...</div>
37+
38+
<div v-if="error">{{ error }}</div>
39+
<ul v-else>
40+
<li v-for="l in data" :key="l.id">{{ l.name }}</li>
41+
</ul>
42+
</template>
43+
```

.changeset/swift-guests-explain.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,27 @@ const { data } = useQuery('SELECT * FROM lists WHERE name = ?', ['aname'], {
1313
}
1414
});
1515
```
16+
17+
- Added the ability to subscribe to an existing instance of a `WatchedQuery`
18+
19+
```jsx
20+
import { useWatchedQuerySubscription } from '@powersync/react';
21+
22+
const listsQuery = powerSync
23+
.query({
24+
sql: `SELECT * FROM lists`
25+
})
26+
.differentialWatch();
27+
28+
export const ListsWidget = (props) => {
29+
const { data: lists } = useWatchedQuerySubscription(listsQuery);
30+
31+
return (
32+
<div>
33+
{lists.map((list) => (
34+
<div key={list.id}>{list.name}</div>
35+
))}
36+
</div>
37+
);
38+
};
39+
```
Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,44 @@
1-
// import { WatchedQuery } from '@powersync/common';
2-
// import { reactive, Ref, ref, UnwrapRef, watchEffect } from 'vue';
1+
import { WatchedQuery } from '@powersync/common';
2+
import { reactive, UnwrapNestedRefs, watchEffect } from 'vue';
33

4-
// /**
5-
// * A composable to access and subscribe to the results of an existing {@link WatchedQuery} instance.
6-
// * @example
7-
// * ```vue
8-
// * <script setup>
9-
// * import { useQuery } from '@powersync/vue';
10-
// *
11-
// * const { data, isLoading, isFetching, error} = useQuery(listsQuery);
12-
// * </script>
13-
// *
14-
// * <template>
15-
// * <div v-if="isLoading">Loading...</div>
16-
// * <div v-else-if="isFetching">Updating results...</div>
17-
// *
18-
// * <div v-if="error">{{ error }}</div>
19-
// * <ul v-else>
20-
// * <li v-for="l in data" :key="l.id">{{ l.name }}</li>
21-
// * </ul>
22-
// * </template>
23-
// * ```
24-
// */
25-
// export const useWatchedQuerySubscription = <
26-
// ResultType = unknown,
27-
// Query extends WatchedQuery<ResultType> = WatchedQuery<ResultType>
28-
// >(
29-
// query: Query
30-
// ) => {
31-
// const state = reactive(query.state);
4+
/**
5+
* A composable to access and subscribe to the results of an existing {@link WatchedQuery} instance.
6+
* @example
7+
* ```vue
8+
* <script setup>
9+
* import { useWatchedQuerySubscription } from '@powersync/vue';
10+
*
11+
* const { data, isLoading, isFetching, error} = useWatchedQuerySubscription(listsQuery);
12+
* </script>
13+
*
14+
* <template>
15+
* <div v-if="isLoading">Loading...</div>
16+
* <div v-else-if="isFetching">Updating results...</div>
17+
*
18+
* <div v-if="error">{{ error }}</div>
19+
* <ul v-else>
20+
* <li v-for="l in data" :key="l.id">{{ l.name }}</li>
21+
* </ul>
22+
* </template>
23+
* ```
24+
*/
25+
export const useWatchedQuerySubscription = <
26+
ResultType = unknown,
27+
Query extends WatchedQuery<ResultType> = WatchedQuery<ResultType>
28+
>(
29+
query: Query
30+
): UnwrapNestedRefs<Query['state']> => {
31+
// Creates a reactive variable which will proxy the state
32+
const state = reactive(query.state) as UnwrapNestedRefs<Query['state']>;
3233

33-
// return state;
34-
// };
34+
watchEffect((onCleanup) => {
35+
const dispose = query.registerListener({
36+
onStateChange: (updatedState) => {
37+
Object.assign(state, updatedState);
38+
}
39+
});
40+
onCleanup(() => dispose());
41+
});
42+
43+
return state;
44+
};

packages/vue/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { usePowerSyncStatus } from './composables/usePowerSyncStatus';
44
export { usePowerSyncWatchedQuery } from './composables/usePowerSyncWatchedQuery';
55
export { useQuery } from './composables/useQuery';
66
export { useStatus } from './composables/useStatus';
7+
export { useWatchedQuerySubscription } from './composables/useWatchedQuerySubscription';

packages/vue/tests/useQuery.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { describe, expect, it, onTestFinished, vi } from 'vitest';
55
import { isProxy, isRef, ref } from 'vue';
66
import { createPowerSyncPlugin } from '../src/composables/powerSync';
77
import { useQuery } from '../src/composables/useQuery';
8+
import { useWatchedQuerySubscription } from '../src/composables/useWatchedQuerySubscription';
89
import { withSetup } from './utils';
910

1011
export const openPowerSync = () => {
@@ -106,6 +107,34 @@ describe('useQuery', () => {
106107
);
107108
});
108109

110+
it('should use an existing WatchedQuery instance', async () => {
111+
// This query can be instantiated once and reused.
112+
// The query retains it's state and will not re-fetch the data unless the result changes.
113+
// This is useful for queries that are used in multiple components.
114+
const listsQuery = powersync!
115+
.query<{ id: string; name: string }>({ sql: `SELECT * FROM lists`, parameters: [] })
116+
.differentialWatch();
117+
118+
const [state] = withPowerSyncSetup(() => useWatchedQuerySubscription(listsQuery));
119+
120+
await powersync!.execute(
121+
/* sql */ `
122+
INSERT INTO
123+
lists (id, name)
124+
VALUES
125+
(uuid (), ?)
126+
`,
127+
['test']
128+
);
129+
130+
await vi.waitFor(
131+
() => {
132+
expect(state.data.length).eq(1);
133+
},
134+
{ timeout: 1000 }
135+
);
136+
});
137+
109138
it('should rerun the query when refresh is used', async () => {
110139
const getAllSpy = vi.spyOn(powersync!, 'getAll');
111140

0 commit comments

Comments
 (0)