Skip to content

Commit a98eb00

Browse files
Revert breaking changes for react package.
1 parent d4cf238 commit a98eb00

File tree

10 files changed

+147
-50
lines changed

10 files changed

+147
-50
lines changed

.changeset/nine-pens-ring.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
'@powersync/vue': minor
33
---
44

5-
- Added the ability to limit re-renders by specifying a `comparator` for query results. The `useQuery` hook will only emit `data` changes when the data has changed.
5+
[Potentially breaking change] The `useQuery` hook results are now explicitly defined as readonly. These values should not be mutated.
6+
7+
- Added the ability to limit re-renders by specifying a `differentiator` for query results. The `useQuery` hook will only emit `data` changes when the data has changed.
68

79
```javascript
8-
useQuery('SELECT * FROM lists WHERE name = ?', ['todo'], {
9-
// This will be used to compare result sets between internal queries
10-
comparator: new ArrayComparator({
11-
compareBy: (item) => JSON.stringify(item)
12-
})
13-
}),
10+
// The data here will maintain previous object references for unchanged items.
11+
const { data } = useQuery('SELECT * FROM lists WHERE name = ?', ['aname'], {
12+
differentiator: {
13+
identify: (item) => item.id,
14+
compareBy: (item) => JSON.stringify(item)
15+
}
16+
});
1417
```

.changeset/swift-guests-explain.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
'@powersync/react': minor
33
---
44

5-
- Added the ability to limit re-renders by specifying a `comparator` for query results. The `useQuery` hook will only emit `data` changes when the data has changed.
5+
- Added the ability to limit re-renders by specifying a `differentiator` for query results. The `useQuery` hook will only emit `data` changes when the data has changed.
66

77
```javascript
8-
useQuery('SELECT * FROM lists WHERE name = ?', ['todo'], {
9-
// This will be used to compare result sets between internal queries
10-
comparator: new ArrayComparator({
11-
compareBy: (item) => JSON.stringify(item)
12-
})
13-
}),
8+
// The data here will maintain previous object references for unchanged items.
9+
const { data } = useQuery('SELECT * FROM lists WHERE name = ?', ['aname'], {
10+
differentiator: {
11+
identify: (item) => item.id,
12+
compareBy: (item) => JSON.stringify(item)
13+
}
14+
});
1415
```

.changeset/tricky-bottles-greet.md

Lines changed: 0 additions & 6 deletions
This file was deleted.

packages/react/src/QueryStore.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import {
44
WatchedQuery,
55
WatchedQueryListenerEvent
66
} from '@powersync/common';
7-
import { AdditionalOptions } from './hooks/watched/watch-types';
7+
import { DifferentialHookOptions } from './hooks/watched/watch-types';
88

99
export function generateQueryKey(
1010
sqlStatement: string,
1111
parameters: ReadonlyArray<unknown>,
12-
options: AdditionalOptions
12+
options: DifferentialHookOptions<unknown>
1313
): string {
1414
return `${sqlStatement} -- ${JSON.stringify(parameters)} -- ${JSON.stringify(options)}`;
1515
}
@@ -19,7 +19,7 @@ export class QueryStore {
1919

2020
constructor(private db: AbstractPowerSyncDatabase) {}
2121

22-
getQuery<RowType>(key: string, query: WatchCompatibleQuery<RowType[]>, options: AdditionalOptions) {
22+
getQuery<RowType>(key: string, query: WatchCompatibleQuery<RowType[]>, options: DifferentialHookOptions<RowType>) {
2323
if (this.cache.has(key)) {
2424
return this.cache.get(key) as WatchedQuery<RowType[]>;
2525
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
import { QueryResult } from '../watched/watch-types';
1+
import { QueryResult, ReadonlyQueryResult } from '../watched/watch-types';
22

33
export type SuspenseQueryResult<T> = Pick<QueryResult<T>, 'data' | 'refresh'>;
4+
export type ReadonlySuspenseQueryResult<T> = Pick<ReadonlyQueryResult<T>, 'data' | 'refresh'>;
Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { CompilableQuery } from '@powersync/common';
2-
import { AdditionalOptions } from '../watched/watch-types';
3-
import { SuspenseQueryResult } from './SuspenseQueryResult';
2+
import { AdditionalOptions, DifferentialHookOptions } from '../watched/watch-types';
3+
import { ReadonlySuspenseQueryResult, SuspenseQueryResult } from './SuspenseQueryResult';
44
import { useSingleSuspenseQuery } from './useSingleSuspenseQuery';
55
import { useWatchedSuspenseQuery } from './useWatchedSuspenseQuery';
66

77
/**
88
* A hook to access the results of a watched query that suspends until the initial result has loaded.
99
* @example
1010
* export const ContentComponent = () => {
11+
* // The lists array here will be a new Array reference whenever a change to the
12+
* // lists table is made.
1113
* const { data: lists } = useSuspenseQuery('SELECT * from lists');
1214
*
1315
* return <View>
@@ -24,16 +26,51 @@ import { useWatchedSuspenseQuery } from './useWatchedSuspenseQuery';
2426
* </Suspense>
2527
* );
2628
* }
29+
*
30+
* export const DiffContentComponent = () => {
31+
* // A differential query will emit results when a change to the result set occurs.
32+
* // The internal array object references are maintained for unchanged rows.
33+
* // The returned lists array is read only when a `differentiator` is provided.
34+
* const { data: lists } = useSuspenseQuery('SELECT * from lists', [], {
35+
* differentiator: {
36+
* identify: (item) => item.id,
37+
* compareBy: (item) => JSON.stringify(item)
38+
* }
39+
* });
40+
* return <View>
41+
* {lists.map((l) => (
42+
* <Text key={l.id}>{JSON.stringify(l)}</Text>
43+
* ))}
44+
* </View>;
45+
* }
46+
*
47+
* export const DisplayComponent = () => {
48+
* return (
49+
* <Suspense fallback={<div>Loading content...</div>}>
50+
* <ContentComponent />
51+
* </Suspense>
52+
* );
53+
* }
2754
*/
28-
export const useSuspenseQuery = <T = any>(
29-
query: string | CompilableQuery<T>,
55+
export function useSuspenseQuery<RowType = any>(
56+
query: string | CompilableQuery<RowType>,
57+
parameters?: any[],
58+
options?: AdditionalOptions
59+
): SuspenseQueryResult<RowType>;
60+
export function useSuspenseQuery<RowType = any>(
61+
query: string | CompilableQuery<RowType>,
62+
paramerers?: any[],
63+
options?: DifferentialHookOptions<RowType>
64+
): ReadonlySuspenseQueryResult<RowType>;
65+
export function useSuspenseQuery<RowType = any>(
66+
query: string | CompilableQuery<RowType>,
3067
parameters: any[] = [],
31-
options: AdditionalOptions<T> = {}
32-
): SuspenseQueryResult<T> => {
33-
switch (options.runQueryOnce) {
68+
options: AdditionalOptions & DifferentialHookOptions<RowType> = {}
69+
) {
70+
switch (options?.runQueryOnce) {
3471
case true:
35-
return useSingleSuspenseQuery<T>(query, parameters, options);
72+
return useSingleSuspenseQuery<RowType>(query, parameters, options);
3673
default:
37-
return useWatchedSuspenseQuery<T>(query, parameters, options);
74+
return useWatchedSuspenseQuery<RowType>(query, parameters, options);
3875
}
39-
};
76+
}

packages/react/src/hooks/suspense/useWatchedSuspenseQuery.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ import { generateQueryKey, getQueryStore } from '../../QueryStore';
33
import { usePowerSync } from '../PowerSyncContext';
44
import { AdditionalOptions } from '../watched/watch-types';
55
import { constructCompatibleQuery } from '../watched/watch-utils';
6-
import { SuspenseQueryResult } from './SuspenseQueryResult';
76
import { useWatchedQuerySuspenseSubscription } from './useWatchedQuerySuspenseSubscription';
87

8+
/**
9+
* @internal This is not exported in the index.ts
10+
*/
911
export const useWatchedSuspenseQuery = <T = any>(
1012
query: string | CompilableQuery<T>,
1113
parameters: any[] = [],
1214
options: AdditionalOptions = {}
13-
): SuspenseQueryResult<T> => {
15+
) => {
1416
const powerSync = usePowerSync();
1517
if (!powerSync) {
1618
throw new Error('PowerSync not configured.');

packages/react/src/hooks/watched/useQuery.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import { type CompilableQuery } from '@powersync/common';
22
import { usePowerSync } from '../PowerSyncContext';
33
import { useSingleQuery } from './useSingleQuery';
44
import { useWatchedQuery } from './useWatchedQuery';
5-
import { AdditionalOptions, QueryResult } from './watch-types';
5+
import { AdditionalOptions, DifferentialHookOptions, QueryResult, ReadonlyQueryResult } from './watch-types';
66
import { constructCompatibleQuery } from './watch-utils';
77

88
/**
99
* A hook to access the results of a watched query.
1010
* @example
11+
*
1112
* export const Component = () => {
13+
* // The lists array here will be a new Array reference whenever a change to the
14+
* // lists table is made.
1215
* const { data: lists } = useQuery('SELECT * from lists');
1316
*
1417
* return <View>
@@ -17,20 +20,48 @@ import { constructCompatibleQuery } from './watch-utils';
1720
* ))}
1821
* </View>
1922
* }
23+
*
24+
* export const DiffComponent = () => {
25+
* // A differential query will emit results when a change to the result set occurs.
26+
* // The internal array object references are maintained for unchanged rows.
27+
* // The returned lists array is read only when a `differentiator` is provided.
28+
* const { data: lists } = useQuery('SELECT * from lists', [], {
29+
* differentiator: {
30+
* identify: (item) => item.id,
31+
* compareBy: (item) => JSON.stringify(item)
32+
* }
33+
* });
34+
*
35+
* return <View>
36+
* {lists.map((l) => (
37+
* <Text key={l.id}>{JSON.stringify(l)}</Text>
38+
* ))}
39+
* </View>
40+
* }
2041
*/
21-
export const useQuery = <RowType = any>(
42+
43+
export function useQuery<RowType = any>(
44+
query: string | CompilableQuery<RowType>,
45+
parameters?: any[],
46+
options?: AdditionalOptions
47+
): QueryResult<RowType>;
48+
export function useQuery<RowType = any>(
49+
query: string | CompilableQuery<RowType>,
50+
paramerers?: any[],
51+
options?: DifferentialHookOptions<RowType>
52+
): ReadonlyQueryResult<RowType>;
53+
export function useQuery<RowType = any>(
2254
query: string | CompilableQuery<RowType>,
2355
parameters: any[] = [],
24-
options: AdditionalOptions<RowType> = { runQueryOnce: false }
25-
): QueryResult<RowType> => {
56+
options: AdditionalOptions & DifferentialHookOptions<RowType> = {}
57+
) {
2658
const powerSync = usePowerSync();
2759
if (!powerSync) {
2860
return { isLoading: false, isFetching: false, data: [], error: new Error('PowerSync not configured.') };
2961
}
30-
3162
const { parsedQuery, queryChanged } = constructCompatibleQuery(query, parameters, options);
3263

33-
switch (options.runQueryOnce) {
64+
switch (options?.runQueryOnce) {
3465
case true:
3566
return useSingleQuery<RowType>({
3667
query: parsedQuery,
@@ -51,4 +82,4 @@ export const useQuery = <RowType = any>(
5182
}
5283
});
5384
}
54-
};
85+
}

packages/react/src/hooks/watched/useWatchedQuery.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import React from 'react';
22
import { useWatchedQuerySubscription } from './useWatchedQuerySubscription';
3-
import { HookWatchOptions, QueryResult } from './watch-types';
3+
import { DifferentialHookOptions, QueryResult, ReadonlyQueryResult } from './watch-types';
44
import { InternalHookOptions } from './watch-utils';
55

6+
/**
7+
* @internal This is not exported from the index.ts
8+
*
9+
* When a differential query is used the return type is readonly. This is required
10+
* since the implementation requires a stable ref.
11+
* For legacy compatibility we allow mutating when a standard query is used. Mutations should
12+
* not affect the internal implementation in this case.
13+
*/
614
export const useWatchedQuery = <RowType = unknown>(
7-
options: InternalHookOptions<RowType[]> & { options: HookWatchOptions }
8-
): QueryResult<RowType> => {
15+
options: InternalHookOptions<RowType[]> & { options: DifferentialHookOptions<RowType> }
16+
): QueryResult<RowType> | ReadonlyQueryResult<RowType> => {
917
const { query, powerSync, queryChanged, options: hookOptions } = options;
1018

1119
const createWatchedQuery = React.useCallback(() => {

packages/react/src/hooks/watched/watch-types.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { SQLOnChangeOptions, WatchedQueryDifferentiator } from '@powersync/common';
22

3-
export interface HookWatchOptions<RowType = unknown> extends Omit<SQLOnChangeOptions, 'signal'> {
3+
export interface HookWatchOptions extends Omit<SQLOnChangeOptions, 'signal'> {
44
reportFetching?: boolean;
5-
differentiator?: WatchedQueryDifferentiator<RowType>;
65
}
76

8-
export interface AdditionalOptions<RowType = unknown> extends HookWatchOptions<RowType> {
7+
export interface AdditionalOptions extends HookWatchOptions {
98
runQueryOnce?: boolean;
109
}
1110

12-
export type QueryResult<RowType> = {
11+
export interface DifferentialHookOptions<RowType> extends HookWatchOptions {
12+
differentiator?: WatchedQueryDifferentiator<RowType>;
13+
}
14+
15+
export type ReadonlyQueryResult<RowType> = {
1316
readonly data: ReadonlyArray<Readonly<RowType>>;
1417
/**
1518
* Indicates the initial loading state (hard loading). Loading becomes false once the first set of results from the watched query is available or an error occurs.
@@ -25,3 +28,20 @@ export type QueryResult<RowType> = {
2528
*/
2629
refresh?: (signal?: AbortSignal) => Promise<void>;
2730
};
31+
32+
export type QueryResult<RowType> = {
33+
data: RowType[];
34+
/**
35+
* Indicates the initial loading state (hard loading). Loading becomes false once the first set of results from the watched query is available or an error occurs.
36+
*/
37+
isLoading: boolean;
38+
/**
39+
* Indicates whether the query is currently fetching data, is true during the initial load and any time when the query is re-evaluating (useful for large queries).
40+
*/
41+
isFetching: boolean;
42+
error: Error | undefined;
43+
/**
44+
* Function used to run the query again.
45+
*/
46+
refresh?: (signal?: AbortSignal) => Promise<void>;
47+
};

0 commit comments

Comments
 (0)