Skip to content

Commit 9832417

Browse files
cleanup React demo
1 parent 5ba28b4 commit 9832417

File tree

14 files changed

+341
-156
lines changed

14 files changed

+341
-156
lines changed

.prettierrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"jsxBracketSameLine": true,
66
"useTabs": false,
77
"printWidth": 120,
8-
"trailingComma": "none"
8+
"trailingComma": "none",
9+
"plugins": ["prettier-plugin-embed", "prettier-plugin-sql"]
910
}

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@ import { NavigationPage } from '@/components/navigation/NavigationPage';
22
import { Box, Button, Grid, TextField, styled } from '@mui/material';
33
import { DataGrid } from '@mui/x-data-grid';
44
import { useQuery } from '@powersync/react';
5+
import { ArrayComparator } from '@powersync/web';
56
import React from 'react';
67

78
export type LoginFormParams = {
89
email: string;
910
password: string;
1011
};
1112

12-
const DEFAULT_QUERY = 'SELECT * FROM lists';
13+
const DEFAULT_QUERY = /* sql */ `
14+
SELECT
15+
*
16+
FROM
17+
lists
18+
`;
1319

14-
const TableDisplay = ({ data }: { data: any[] }) => {
15-
console.log('Rendering table display', data);
20+
const TableDisplay = React.memo(({ data }: { data: any[] }) => {
1621
const queryDataGridResult = React.useMemo(() => {
1722
const firstItem = data?.[0];
1823
return {
@@ -44,15 +49,28 @@ const TableDisplay = ({ data }: { data: any[] }) => {
4449
/>
4550
</S.QueryResultContainer>
4651
);
47-
};
52+
});
53+
4854
export default function SQLConsolePage() {
4955
const inputRef = React.useRef<HTMLInputElement>();
5056
const [query, setQuery] = React.useState(DEFAULT_QUERY);
51-
const { data } = useQuery(query, [], { reportFetching: false });
5257

53-
React.useEffect(() => {
54-
console.log('Query result changed', data);
55-
}, [data]);
58+
const { data } = useQuery(query, [], {
59+
/**
60+
* We don't use the isFetching status here, we can avoid re-renders if we don't report on it.
61+
*/
62+
reportFetching: false,
63+
/**
64+
* The query here will only emit results when the query data set changes.
65+
* Result sets are compared by serializing each item to JSON and comparing the strings.
66+
*/
67+
processor: {
68+
mode: 'comparison',
69+
comparator: new ArrayComparator({
70+
compareBy: (item) => JSON.stringify(item)
71+
})
72+
}
73+
});
5674

5775
return (
5876
<NavigationPage title="SQL Console">

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

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { usePowerSync, useQuery } from '@powersync/react';
1+
import { NavigationPage } from '@/components/navigation/NavigationPage';
2+
import { useSupabase } from '@/components/providers/SystemProvider';
3+
import { TodoItemWidget } from '@/components/widgets/TodoItemWidget';
4+
import { LISTS_TABLE, TODOS_TABLE, TodoRecord } from '@/library/powersync/AppSchema';
25
import AddIcon from '@mui/icons-material/Add';
36
import {
47
Box,
@@ -15,12 +18,9 @@ import {
1518
styled
1619
} from '@mui/material';
1720
import Fab from '@mui/material/Fab';
21+
import { usePowerSync, useQuery } from '@powersync/react';
1822
import React, { Suspense } from 'react';
1923
import { useParams } from 'react-router-dom';
20-
import { useSupabase } from '@/components/providers/SystemProvider';
21-
import { LISTS_TABLE, TODOS_TABLE, TodoRecord } from '@/library/powersync/AppSchema';
22-
import { NavigationPage } from '@/components/navigation/NavigationPage';
23-
import { TodoItemWidget } from '@/components/widgets/TodoItemWidget';
2424

2525
/**
2626
* useSearchParams causes the entire element to fall back to client side rendering
@@ -34,61 +34,53 @@ const TodoEditSection = () => {
3434

3535
const {
3636
data: [listRecord]
37-
} = useQuery<{ name: string }>(`SELECT name FROM ${LISTS_TABLE} WHERE id = ?`, [listID]);
37+
} = useQuery<{ name: string }>(
38+
/* sql */ `
39+
SELECT
40+
name
41+
FROM
42+
${LISTS_TABLE}
43+
WHERE
44+
id = ?
45+
`,
46+
[listID]
47+
);
3848

3949
const { data: todos } = useQuery<TodoRecord>(
40-
`SELECT * FROM ${TODOS_TABLE} WHERE list_id=? ORDER BY created_at DESC, id`,
50+
/* sql */ `
51+
SELECT
52+
*
53+
FROM
54+
${TODOS_TABLE}
55+
WHERE
56+
list_id = ?
57+
ORDER BY
58+
created_at DESC,
59+
id
60+
`,
4161
[listID]
4262
);
4363

4464
const [showPrompt, setShowPrompt] = React.useState(false);
4565
const nameInputRef = React.createRef<HTMLInputElement>();
4666

47-
const toggleCompletion = async (record: TodoRecord, completed: boolean) => {
48-
const updatedRecord = { ...record, completed: completed };
49-
if (completed) {
50-
const userID = supabase?.currentSession?.user.id;
51-
if (!userID) {
52-
throw new Error(`Could not get user ID.`);
53-
}
54-
updatedRecord.completed_at = new Date().toISOString();
55-
updatedRecord.completed_by = userID;
56-
} else {
57-
updatedRecord.completed_at = null;
58-
updatedRecord.completed_by = null;
59-
}
60-
await powerSync.execute(
61-
`UPDATE ${TODOS_TABLE}
62-
SET completed = ?,
63-
completed_at = ?,
64-
completed_by = ?
65-
WHERE id = ?`,
66-
[completed, updatedRecord.completed_at, updatedRecord.completed_by, record.id]
67-
);
68-
};
69-
7067
const createNewTodo = async (description: string) => {
7168
const userID = supabase?.currentSession?.user.id;
7269
if (!userID) {
7370
throw new Error(`Could not get user ID.`);
7471
}
7572

7673
await powerSync.execute(
77-
`INSERT INTO
78-
${TODOS_TABLE}
79-
(id, created_at, created_by, description, list_id)
80-
VALUES
81-
(uuid(), datetime(), ?, ?, ?)`,
74+
/* sql */ `
75+
INSERT INTO
76+
${TODOS_TABLE} (id, created_at, created_by, description, list_id)
77+
VALUES
78+
(uuid (), datetime (), ?, ?, ?)
79+
`,
8280
[userID, description, listID!]
8381
);
8482
};
8583

86-
const deleteTodo = async (id: string) => {
87-
await powerSync.writeTransaction(async (tx) => {
88-
await tx.execute(`DELETE FROM ${TODOS_TABLE} WHERE id = ?`, [id]);
89-
});
90-
};
91-
9284
if (!listRecord) {
9385
return (
9486
<Box>
@@ -106,13 +98,7 @@ const TodoEditSection = () => {
10698
<Box>
10799
<List dense={false}>
108100
{todos.map((r) => (
109-
<TodoItemWidget
110-
key={r.id}
111-
description={r.description}
112-
onDelete={() => deleteTodo(r.id)}
113-
isComplete={r.completed == 1}
114-
toggleCompletion={() => toggleCompletion(r, !r.completed)}
115-
/>
101+
<TodoItemWidget key={r.id} id={r.id} description={r.description} isComplete={r.completed == 1} />
116102
))}
117103
</List>
118104
</Box>
@@ -129,8 +115,7 @@ const TodoEditSection = () => {
129115
await createNewTodo(nameInputRef.current!.value);
130116
setShowPrompt(false);
131117
}
132-
}}
133-
>
118+
}}>
134119
<DialogTitle id="alert-dialog-title">{'Create Todo Item'}</DialogTitle>
135120
<DialogContent>
136121
<DialogContentText id="alert-dialog-description">Enter a description for a new todo item</DialogContentText>

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { usePowerSync, useStatus } from '@powersync/react';
1+
import { NavigationPage } from '@/components/navigation/NavigationPage';
2+
import { useSupabase } from '@/components/providers/SystemProvider';
3+
import { GuardBySync } from '@/components/widgets/GuardBySync';
4+
import { SearchBarWidget } from '@/components/widgets/SearchBarWidget';
5+
import { TodoListsWidget } from '@/components/widgets/TodoListsWidget';
6+
import { LISTS_TABLE } from '@/library/powersync/AppSchema';
27
import AddIcon from '@mui/icons-material/Add';
38
import {
49
Box,
@@ -12,18 +17,12 @@ import {
1217
styled
1318
} from '@mui/material';
1419
import Fab from '@mui/material/Fab';
20+
import { usePowerSync } from '@powersync/react';
1521
import React from 'react';
16-
import { useSupabase } from '@/components/providers/SystemProvider';
17-
import { LISTS_TABLE } from '@/library/powersync/AppSchema';
18-
import { NavigationPage } from '@/components/navigation/NavigationPage';
19-
import { SearchBarWidget } from '@/components/widgets/SearchBarWidget';
20-
import { TodoListsWidget } from '@/components/widgets/TodoListsWidget';
21-
import { GuardBySync } from '@/components/widgets/GuardBySync';
2222

2323
export default function TodoListsPage() {
2424
const powerSync = usePowerSync();
2525
const supabase = useSupabase();
26-
const status = useStatus();
2726

2827
const [showPrompt, setShowPrompt] = React.useState(false);
2928
const nameInputRef = React.createRef<HTMLInputElement>();
@@ -36,7 +35,12 @@ export default function TodoListsPage() {
3635
}
3736

3837
const res = await powerSync.execute(
39-
`INSERT INTO ${LISTS_TABLE} (id, created_at, name, owner_id) VALUES (uuid(), datetime(), ?, ?) RETURNING *`,
38+
/* sql */ `
39+
INSERT INTO
40+
${LISTS_TABLE} (id, created_at, name, owner_id)
41+
VALUES
42+
(uuid (), datetime (), ?, ?) RETURNING *
43+
`,
4044
[name, userID]
4145
);
4246

@@ -71,8 +75,7 @@ export default function TodoListsPage() {
7175
}
7276
}}
7377
aria-labelledby="alert-dialog-title"
74-
aria-describedby="alert-dialog-description"
75-
>
78+
aria-describedby="alert-dialog-description">
7679
<DialogTitle id="alert-dialog-title">{'Create Todo List'}</DialogTitle>
7780
<DialogContent>
7881
<DialogContentText id="alert-dialog-description">Enter a name for a new todo list</DialogContentText>

demos/react-supabase-todolist/src/components/widgets/ListItemWidget.tsx

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,60 +11,80 @@ import {
1111
} from '@mui/material';
1212
import React from 'react';
1313

14+
import { TODO_LISTS_ROUTE } from '@/app/router';
15+
import { LISTS_TABLE, TODOS_TABLE } from '@/library/powersync/AppSchema';
1416
import RightIcon from '@mui/icons-material/ArrowRightAlt';
1517
import DeleteIcon from '@mui/icons-material/DeleteOutline';
1618
import ListIcon from '@mui/icons-material/ListAltOutlined';
19+
import { usePowerSync } from '@powersync/react';
20+
import { useNavigate } from 'react-router-dom';
1721

1822
export type ListItemWidgetProps = {
23+
id: string;
1924
title: string;
2025
description: string;
2126
selected?: boolean;
22-
onDelete: () => void;
23-
onPress: () => void;
2427
};
2528

26-
export const ListItemWidget: React.FC<ListItemWidgetProps> = (props) => {
27-
console.log('ListItemWidget', props);
29+
export const ListItemWidget: React.FC<ListItemWidgetProps> = React.memo((props) => {
30+
const { id, title, description, selected } = props;
31+
32+
const powerSync = usePowerSync();
33+
const navigate = useNavigate();
34+
35+
const deleteList = React.useCallback(async () => {
36+
await powerSync.writeTransaction(async (tx) => {
37+
// Delete associated todos
38+
await tx.execute(
39+
/* sql */ `
40+
DELETE FROM ${TODOS_TABLE}
41+
WHERE
42+
list_id = ?
43+
`,
44+
[id]
45+
);
46+
// Delete list record
47+
await tx.execute(
48+
/* sql */ `
49+
DELETE FROM ${LISTS_TABLE}
50+
WHERE
51+
id = ?
52+
`,
53+
[id]
54+
);
55+
});
56+
}, [id]);
57+
58+
const openList = React.useCallback(() => {
59+
navigate(TODO_LISTS_ROUTE + '/' + id);
60+
}, [id]);
61+
2862
return (
2963
<S.MainPaper elevation={1}>
3064
<ListItem
3165
disablePadding
3266
secondaryAction={
3367
<Box>
34-
<IconButton
35-
edge="end"
36-
aria-label="delete"
37-
onClick={(event) => {
38-
props.onDelete();
39-
}}>
68+
<IconButton edge="end" aria-label="delete" onClick={deleteList}>
4069
<DeleteIcon />
4170
</IconButton>
42-
<IconButton
43-
edge="end"
44-
aria-label="proceed"
45-
onClick={(event) => {
46-
props.onPress();
47-
}}>
71+
<IconButton edge="end" aria-label="proceed" onClick={openList}>
4872
<RightIcon />
4973
</IconButton>
5074
</Box>
5175
}>
52-
<ListItemButton
53-
onClick={(event) => {
54-
props.onPress();
55-
}}
56-
selected={props.selected}>
76+
<ListItemButton onClick={openList} selected={selected}>
5777
<ListItemAvatar>
5878
<Avatar>
5979
<ListIcon />
6080
</Avatar>
6181
</ListItemAvatar>
62-
<ListItemText primary={props.title} secondary={props.description} />
82+
<ListItemText primary={title} secondary={description} />
6383
</ListItemButton>
6484
</ListItem>
6585
</S.MainPaper>
6686
);
67-
};
87+
});
6888

6989
export namespace S {
7090
export const MainPaper = styled(Paper)`

0 commit comments

Comments
 (0)