Skip to content

Commit d62f367

Browse files
DominicGBauerDominicGBauer
andauthored
feat(react): add useQuery and useStatus hooks (#142)
Co-authored-by: DominicGBauer <dominic@nomanini.com>
1 parent cbd0ce1 commit d62f367

File tree

33 files changed

+453
-139
lines changed

33 files changed

+453
-139
lines changed

.changeset/strange-spiders-study.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"react-native-supabase-group-chat": minor
3+
"react-native-supabase-todolist": minor
4+
"yjs-react-supabase-text-collab": minor
5+
"django-react-native-todolist": minor
6+
"react-supabase-todolist": minor
7+
"example-nextjs": minor
8+
"@powersync/react": minor
9+
---
10+
11+
Deprecate usePowerSyncStatus, usePowerSyncQuery and usePowerSyncWatchedQuery in favor of useQuery and useStatus

demos/django-react-native-todolist/library/widgets/HeaderWidget.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Alert, Text } from 'react-native';
33
import { Icon } from 'react-native-elements';
44
import { useNavigation } from 'expo-router';
55
import { useSystem } from '../stores/system';
6-
import { usePowerSyncStatus } from '@powersync/react';
6+
import { useStatus } from '@powersync/react';
77
import { Header } from 'react-native-elements';
88
import { observer } from 'mobx-react-lite';
99
import { DrawerActions } from '@react-navigation/native';
@@ -13,7 +13,7 @@ export const HeaderWidget: React.FC<{
1313
}> = observer((props) => {
1414
const { title } = props;
1515
const { powersync } = useSystem();
16-
const status = usePowerSyncStatus();
16+
const status = useStatus();
1717
const navigation = useNavigation();
1818

1919
return (

demos/example-nextjs/src/app/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
import React, { useEffect } from 'react';
44
import { CircularProgress, Grid, ListItem, styled } from '@mui/material';
5-
import { useRouter } from 'next/navigation';
6-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react';
5+
import { usePowerSync, useQuery } from '@powersync/react';
76

87
export default function EntryPage() {
9-
const router = useRouter();
108
const db = usePowerSync();
11-
const customers = usePowerSyncWatchedQuery('SELECT id, name FROM customers');
9+
const { data: customers, isLoading } = useQuery('SELECT id, name FROM customers');
1210

1311
useEffect(() => {
1412
// Insert some test data
@@ -18,6 +16,10 @@ export default function EntryPage() {
1816
return () => {};
1917
}, []);
2018

19+
if (isLoading) {
20+
return <CircularProgress />;
21+
}
22+
2123
return (
2224
<S.MainGrid container>
2325
<S.CenteredGrid item xs={12} md={6} lg={5}>

demos/react-native-supabase-group-chat/src/app/(app)/(chats)/c/[profile]/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { faker } from '@faker-js/faker';
2-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react-native';
2+
import { usePowerSync, useQuery } from '@powersync/react-native';
33
import { FlashList } from '@shopify/flash-list';
44
import { Stack, useLocalSearchParams } from 'expo-router';
55
import { useEffect, useState } from 'react';
@@ -13,12 +13,12 @@ export default function ChatsChatIndex() {
1313
const { profile: profileId } = useLocalSearchParams<{ profile: string }>();
1414
const { user } = useAuth();
1515
const powerSync = usePowerSync();
16-
const profiles = usePowerSyncWatchedQuery('SELECT id, name, handle, demo FROM profiles WHERE id = ?', [profileId]);
16+
const { data: profiles } = useQuery('SELECT id, name, handle, demo FROM profiles WHERE id = ?', [profileId]);
1717
const profile = profiles.length ? profiles[0] : undefined;
1818
const [draftId, setDraftId] = useState<string>();
1919
const [listMessages, setListMessages] = useState<any[]>([]);
2020

21-
const messages = usePowerSyncWatchedQuery(
21+
const { data: messages } = useQuery(
2222
'SELECT sender_id, content, created_at FROM messages WHERE (((sender_id = ?1 AND recipient_id = ?2) OR (sender_id = ?2 AND recipient_id = ?1)) AND NOT (sender_id = ?1 AND sent_at IS NULL)) ORDER BY created_at ASC',
2323
[user?.id, profile?.id]
2424
);

demos/react-native-supabase-group-chat/src/app/(app)/(chats)/g/[group]/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react-native';
1+
import { usePowerSync, useQuery } from '@powersync/react-native';
22
import { FlashList } from '@shopify/flash-list';
33
import { Stack, useLocalSearchParams } from 'expo-router';
44
import { useState } from 'react';
@@ -12,10 +12,10 @@ export default function ChatsChatIndex() {
1212
const { group: groupId } = useLocalSearchParams<{ group: string }>();
1313
const { user } = useAuth();
1414
const powerSync = usePowerSync();
15-
const groups = usePowerSyncWatchedQuery('SELECT id, name FROM groups WHERE id = ?', [groupId]);
15+
const { data: groups } = useQuery('SELECT id, name FROM groups WHERE id = ?', [groupId]);
1616
const group = groups.length ? groups[0] : undefined;
1717

18-
const messages = usePowerSyncWatchedQuery(
18+
const { data: messages } = useQuery(
1919
'SELECT sender_id, content, created_at FROM messages WHERE group_id = ? ORDER BY created_at ASC',
2020
[group?.id]
2121
);

demos/react-native-supabase-group-chat/src/app/(app)/(chats)/g/[group]/settings.tsx

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react-native';
1+
import { usePowerSync, useQuery } from '@powersync/react-native';
22
import { Save, Trash, XCircle } from '@tamagui/lucide-icons';
33
import { useLocalSearchParams, useRouter } from 'expo-router';
44
import { useEffect, useState } from 'react';
@@ -16,8 +16,8 @@ export default function GroupSettings() {
1616
const [selectedContacts, setSelectedContacts] = useState<Set<string>>(new Set());
1717
const powerSync = usePowerSync();
1818

19-
const groups = usePowerSyncWatchedQuery('SELECT name FROM groups WHERE id = ?', [groupId]);
20-
const groupMembers = usePowerSyncWatchedQuery('SELECT profile_id FROM memberships WHERE group_id = ?', [groupId]);
19+
const { data: groups } = useQuery('SELECT name FROM groups WHERE id = ?', [groupId]);
20+
const { data: groupMembers } = useQuery('SELECT profile_id FROM memberships WHERE group_id = ?', [groupId]);
2121

2222
useEffect(() => {
2323
if (groups.length > 0) {
@@ -74,16 +74,13 @@ export default function GroupSettings() {
7474

7575
await powerSync.writeTransaction(async (tx) => {
7676
try {
77-
await tx.executeAsync('UPDATE groups SET name= ? WHERE id = ?', [name, groupId]);
77+
await tx.execute('UPDATE groups SET name= ? WHERE id = ?', [name, groupId]);
7878
for (const profileId of removedContacts) {
79-
const result = await tx.executeAsync('DELETE FROM memberships WHERE group_id = ? AND profile_id = ?', [
80-
groupId,
81-
profileId
82-
]);
79+
await tx.execute('DELETE FROM memberships WHERE group_id = ? AND profile_id = ?', [groupId, profileId]);
8380
}
8481
for (const profileId of addedContacts) {
8582
const membershipId = uuid();
86-
const result = await tx.executeAsync(
83+
await tx.execute(
8784
'INSERT INTO memberships (id, group_id, profile_id, created_at) VALUES (?, ?, ?, datetime())',
8885
[membershipId, groupId, profileId]
8986
);
@@ -99,9 +96,9 @@ export default function GroupSettings() {
9996
async function deleteTransaction() {
10097
await powerSync.writeTransaction(async (tx) => {
10198
try {
102-
await tx.executeAsync('DELETE FROM memberships WHERE group_id = ?', [groupId]);
103-
await tx.executeAsync('DELETE FROM messages WHERE group_id = ?', [groupId]);
104-
await tx.executeAsync('DELETE FROM groups WHERE id = ?', [groupId]);
99+
await tx.execute('DELETE FROM memberships WHERE group_id = ?', [groupId]);
100+
await tx.execute('DELETE FROM messages WHERE group_id = ?', [groupId]);
101+
await tx.execute('DELETE FROM groups WHERE id = ?', [groupId]);
105102

106103
router.back();
107104
} catch (error) {
@@ -148,8 +145,7 @@ export default function GroupSettings() {
148145
backgroundColor="$red10"
149146
color="white"
150147
onPress={handleDelete}
151-
margin="$3"
152-
>
148+
margin="$3">
153149
Delete group
154150
</Button>
155151
</YStack>

demos/react-native-supabase-group-chat/src/app/(app)/(chats)/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react-native';
1+
import { usePowerSync, useQuery } from '@powersync/react-native';
22
import { MessageSquare, Plus } from '@tamagui/lucide-icons';
33
import { Link, useNavigation } from 'expo-router';
44
import { useEffect, useState } from 'react';
@@ -24,7 +24,7 @@ export default function ChatsIndex() {
2424
return unsubscribe;
2525
}, [navigation]);
2626

27-
const chats = usePowerSyncWatchedQuery(
27+
const { data: chats } = useQuery(
2828
`SELECT profiles.id as partner_id, profiles.name as name, profiles.handle as handle, 'contact' as type, m.created_at as last_message_at FROM chats LEFT JOIN profiles on chats.id = profiles.id LEFT JOIN (SELECT * FROM messages WHERE (sender_id, recipient_id, created_at) IN (SELECT sender_id, recipient_id, MAX(created_at) FROM messages GROUP BY group_id)) as m ON m.recipient_id = chats.id OR m.sender_id = chats.id WHERE (name LIKE '%' || ?1 || '%' OR handle LIKE '%' || ?1 || '%') GROUP BY profiles.id UNION
2929
SELECT groups.id as partner_id, groups.name as name, '' as handle, 'group' as type, m.created_at as last_message_at FROM groups LEFT JOIN (SELECT * FROM messages WHERE (group_id, created_at) IN (SELECT group_id, MAX(created_at) FROM messages GROUP BY group_id)) as m ON m.group_id = groups.id WHERE (name LIKE '%' || ?1 || '%') GROUP BY groups.id ORDER BY last_message_at DESC`,
3030
[search]

demos/react-native-supabase-group-chat/src/app/(app)/contacts/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { faker } from '@faker-js/faker';
2-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react-native';
2+
import { usePowerSync, useQuery } from '@powersync/react-native';
33
import { Search, Shuffle } from '@tamagui/lucide-icons';
44
import { useState } from 'react';
55
import { Button, Input, XStack, YStack } from 'tamagui';
@@ -17,7 +17,7 @@ export default function ContactsIndex() {
1717
const [search, setSearch] = useState<string>('');
1818
const [profiles, setProfiles] = useState<any[]>([]);
1919

20-
const contacts = usePowerSyncWatchedQuery(
20+
const { data: contacts } = useQuery(
2121
"SELECT contacts.id, profiles.id as profile_id, profiles.name, profiles.handle, 'contact' as type FROM contacts LEFT JOIN profiles ON contacts.profile_id = profiles.id WHERE (profiles.name LIKE '%' || ?1 || '%' OR profiles.handle LIKE '%' || ?1 || '%') ORDER BY name ASC",
2222
[search]
2323
);

demos/react-native-supabase-group-chat/src/app/(app)/settings/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react-native';
1+
import { usePowerSync, useQuery } from '@powersync/react-native';
22
import { useEffect, useState } from 'react';
33
import { Button, Input, Label, Switch, Text, XStack, YStack } from 'tamagui';
44

@@ -10,7 +10,7 @@ export default function SettingsIndex() {
1010
const [name, setName] = useState('');
1111
const [handle, setHandle] = useState('');
1212

13-
const profiles = usePowerSyncWatchedQuery('SELECT * FROM profiles WHERE id = ?', [user?.id]);
13+
const { data: profiles } = useQuery('SELECT * FROM profiles WHERE id = ?', [user?.id]);
1414

1515
useEffect(() => {
1616
if (profiles.length > 0) {

demos/react-native-supabase-group-chat/src/components/groups/MemberSelector.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { usePowerSyncWatchedQuery } from '@powersync/react-native';
1+
import { useQuery } from '@powersync/react-native';
22
import { CheckCircle2, Circle } from '@tamagui/lucide-icons';
33
import { useState } from 'react';
44
import { Input, ListItem, XStack, YStack } from 'tamagui';
@@ -15,7 +15,7 @@ export function MemberSelector({
1515
}) {
1616
const [search, setSearch] = useState<string>('');
1717

18-
const contacts = usePowerSyncWatchedQuery(
18+
const { data: contacts } = useQuery(
1919
"SELECT contacts.id, profiles.id as profile_id, profiles.name, profiles.handle, 'contact' as type FROM contacts LEFT JOIN profiles ON contacts.profile_id = profiles.id WHERE (profiles.name LIKE '%' || ?1 || '%' OR profiles.handle LIKE '%' || ?1 || '%') ORDER BY name ASC",
2020
[search]
2121
);

demos/react-native-supabase-todolist/app/views/todos/edit/[id].tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ATTACHMENT_TABLE, AttachmentRecord } from '@powersync/attachments';
2-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react-native';
2+
import { usePowerSync, useQuery } from '@powersync/react-native';
33
import { CameraCapturedPicture } from 'expo-camera';
44
import _ from 'lodash';
55
import * as React from 'react';
@@ -34,11 +34,11 @@ const TodoView: React.FC = () => {
3434
const params = useLocalSearchParams<{ id: string }>();
3535
const listID = params.id;
3636

37-
const [listRecord] = usePowerSyncWatchedQuery<{ name: string }>(`SELECT name FROM ${LIST_TABLE} WHERE id = ?`, [
38-
listID
39-
]);
37+
const {
38+
data: [listRecord]
39+
} = useQuery<{ name: string }>(`SELECT name FROM ${LIST_TABLE} WHERE id = ?`, [listID]);
4040

41-
const todos = usePowerSyncWatchedQuery<TodoEntry>(
41+
const { data: todos, isLoading } = useQuery<TodoEntry>(
4242
`
4343
SELECT
4444
${TODO_TABLE}.id AS todo_id,
@@ -105,6 +105,12 @@ const TodoView: React.FC = () => {
105105
});
106106
};
107107

108+
if (isLoading) {
109+
<View>
110+
<Text>Loading...</Text>
111+
</View>;
112+
}
113+
108114
if (listRecord == null) {
109115
return (
110116
<View>

demos/react-native-supabase-todolist/app/views/todos/lists.tsx

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import prompt from 'react-native-prompt-android';
77
import { router, Stack } from 'expo-router';
88
import { LIST_TABLE, TODO_TABLE, ListRecord } from '../../../library/powersync/AppSchema';
99
import { useSystem } from '../../../library/powersync/system';
10-
import { usePowerSyncWatchedQuery } from '@powersync/react-native';
10+
import { useQuery, useStatus } from '@powersync/react-native';
1111
import { ListItemWidget } from '../../../library/widgets/ListItemWidget';
1212

1313
const description = (total: number, completed: number = 0) => {
@@ -16,7 +16,8 @@ const description = (total: number, completed: number = 0) => {
1616

1717
const ListsViewWidget: React.FC = () => {
1818
const system = useSystem();
19-
const listRecords = usePowerSyncWatchedQuery<ListRecord & { total_tasks: number; completed_tasks: number }>(`
19+
const status = useStatus();
20+
const { data: listRecords } = useQuery<ListRecord & { total_tasks: number; completed_tasks: number }>(`
2021
SELECT
2122
${LIST_TABLE}.*, COUNT(${TODO_TABLE}.id) AS total_tasks, SUM(CASE WHEN ${TODO_TABLE}.completed = true THEN 1 ELSE 0 END) as completed_tasks
2223
FROM
@@ -77,20 +78,24 @@ const ListsViewWidget: React.FC = () => {
7778
}}
7879
/>
7980
<ScrollView key={'lists'} style={{ maxHeight: '90%' }}>
80-
{listRecords.map((r) => (
81-
<ListItemWidget
82-
key={r.id}
83-
title={r.name}
84-
description={description(r.total_tasks, r.completed_tasks)}
85-
onDelete={() => deleteList(r.id)}
86-
onPress={() => {
87-
router.push({
88-
pathname: 'views/todos/edit/[id]',
89-
params: { id: r.id }
90-
});
91-
}}
92-
/>
93-
))}
81+
{!status.hasSynced ? (
82+
<p>Busy with sync...</p>
83+
) : (
84+
listRecords.map((r) => (
85+
<ListItemWidget
86+
key={r.id}
87+
title={r.name}
88+
description={description(r.total_tasks, r.completed_tasks)}
89+
onDelete={() => deleteList(r.id)}
90+
onPress={() => {
91+
router.push({
92+
pathname: 'views/todos/edit/[id]',
93+
params: { id: r.id }
94+
});
95+
}}
96+
/>
97+
))
98+
)}
9499
</ScrollView>
95100

96101
<StatusBar style={'light'} />

demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Alert, Text } from 'react-native';
33
import { Icon } from 'react-native-elements';
44
import { useNavigation } from 'expo-router';
55
import { Header } from 'react-native-elements';
6-
import { usePowerSyncStatus } from '@powersync/react';
6+
import { useStatus } from '@powersync/react';
77
import { DrawerActions } from '@react-navigation/native';
88
import { useSystem } from '../powersync/system';
99

@@ -13,7 +13,7 @@ export const HeaderWidget: React.FC<{
1313
const system = useSystem();
1414
const { powersync } = system;
1515
const navigation = useNavigation();
16-
const status = usePowerSyncStatus();
16+
const status = useStatus();
1717

1818
const { title } = props;
1919
return (

demos/react-supabase-todolist/src/app/views/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ import React from 'react';
2525

2626
import { useNavigationPanel } from '@/components/navigation/NavigationPanelContext';
2727
import { useSupabase } from '@/components/providers/SystemProvider';
28-
import { usePowerSync, usePowerSyncStatus } from '@powersync/react';
28+
import { usePowerSync, useStatus } from '@powersync/react';
2929
import { useNavigate } from 'react-router-dom';
3030
import { LOGIN_ROUTE, SQL_CONSOLE_ROUTE, TODO_LISTS_ROUTE } from '@/app/router';
3131

3232
export default function ViewsLayout({ children }: { children: React.ReactNode }) {
3333
const powerSync = usePowerSync();
34-
const status = usePowerSyncStatus();
34+
const status = useStatus();
3535
const supabase = useSupabase();
3636
const navigate = useNavigate();
3737

demos/react-supabase-todolist/src/app/views/sql-console/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { usePowerSyncWatchedQuery } from '@powersync/react';
2+
import { useQuery } from '@powersync/react';
33
import { Box, Button, Grid, TextField, styled } from '@mui/material';
44
import { DataGrid } from '@mui/x-data-grid';
55
import { NavigationPage } from '@/components/navigation/NavigationPage';
@@ -14,7 +14,7 @@ const DEFAULT_QUERY = 'SELECT * FROM lists';
1414
export default function SQLConsolePage() {
1515
const inputRef = React.useRef<HTMLInputElement>();
1616
const [query, setQuery] = React.useState(DEFAULT_QUERY);
17-
const querySQLResult = usePowerSyncWatchedQuery(query);
17+
const { data: querySQLResult } = useQuery(query);
1818

1919
const queryDataGridResult = React.useMemo(() => {
2020
const firstItem = querySQLResult?.[0];

demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useSupabase } from '@/components/providers/SystemProvider';
22
import { TodoItemWidget } from '@/components/widgets/TodoItemWidget';
33
import { LISTS_TABLE, TODOS_TABLE, TodoRecord } from '@/library/powersync/AppSchema';
4-
import { usePowerSync, usePowerSyncWatchedQuery } from '@powersync/react';
4+
import { usePowerSync, useQuery } from '@powersync/react';
55
import AddIcon from '@mui/icons-material/Add';
66
import {
77
Box,
@@ -32,11 +32,11 @@ const TodoEditSection = () => {
3232
const supabase = useSupabase();
3333
const { id: listID } = useParams();
3434

35-
const [listRecord] = usePowerSyncWatchedQuery<{ name: string }>(`SELECT name FROM ${LISTS_TABLE} WHERE id = ?`, [
36-
listID
37-
]);
35+
const {
36+
data: [listRecord]
37+
} = useQuery<{ name: string }>(`SELECT name FROM ${LISTS_TABLE} WHERE id = ?`, [listID]);
3838

39-
const todos = usePowerSyncWatchedQuery<TodoRecord>(
39+
const { data: todos } = useQuery<TodoRecord>(
4040
`SELECT * FROM ${TODOS_TABLE} WHERE list_id=? ORDER BY created_at DESC, id`,
4141
[listID]
4242
);

0 commit comments

Comments
 (0)