diff --git a/demos/react-supabase-todolist/package.json b/demos/react-supabase-todolist/package.json
index c4a9055f..b2eac20e 100644
--- a/demos/react-supabase-todolist/package.json
+++ b/demos/react-supabase-todolist/package.json
@@ -19,6 +19,7 @@
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-error-boundary": "^4.0.13",
"react-router-dom": "^6.22.3"
},
"devDependencies": {
diff --git a/demos/react-supabase-todolist/src/app/index.tsx b/demos/react-supabase-todolist/src/app/index.tsx
index 450cc12a..eb1169ba 100644
--- a/demos/react-supabase-todolist/src/app/index.tsx
+++ b/demos/react-supabase-todolist/src/app/index.tsx
@@ -8,9 +8,11 @@ const root = createRoot(document.getElementById('app')!);
root.render();
export function App() {
- return
-
-
-
- ;
+ return (
+
+
+
+
+
+ );
}
diff --git a/demos/react-supabase-todolist/src/app/router.tsx b/demos/react-supabase-todolist/src/app/router.tsx
index 8268cc66..1dc30bae 100644
--- a/demos/react-supabase-todolist/src/app/router.tsx
+++ b/demos/react-supabase-todolist/src/app/router.tsx
@@ -2,10 +2,10 @@ import { Outlet, createBrowserRouter } from "react-router-dom";
import LoginPage from "./auth/login/page";
import RegisterPage from "./auth/register/page";
import EntryPage from "./page";
-import TodoEditPage from "./views/todo-lists/edit/page";
-import TodoListsPage from "./views/todo-lists/page";
import ViewsLayout from "./views/layout";
import SQLConsolePage from "./views/sql-console/page";
+import TodoEditPage from "./views/todo-lists/edit/page";
+import TodoListsPage from "./views/todo-lists/page";
export const TODO_LISTS_ROUTE = '/views/todo-lists';
export const TODO_EDIT_ROUTE = '/views/todo-lists/:id';
diff --git a/demos/react-supabase-todolist/src/app/views/layout.tsx b/demos/react-supabase-todolist/src/app/views/layout.tsx
index ccc256c1..b85df2f6 100644
--- a/demos/react-supabase-todolist/src/app/views/layout.tsx
+++ b/demos/react-supabase-todolist/src/app/views/layout.tsx
@@ -21,7 +21,7 @@ import {
Typography,
styled
} from '@mui/material';
-import React from 'react';
+import React, { Suspense } from 'react';
import { useNavigationPanel } from '@/components/navigation/NavigationPanelContext';
import { useSupabase } from '@/components/providers/SystemProvider';
@@ -118,7 +118,9 @@ export default function ViewsLayout({ children }: { children: React.ReactNode })
- {children}
+
+ {children}
+
);
diff --git a/demos/react-supabase-todolist/src/app/views/sql-console/page.tsx b/demos/react-supabase-todolist/src/app/views/sql-console/page.tsx
index 949dff5e..0d8af258 100644
--- a/demos/react-supabase-todolist/src/app/views/sql-console/page.tsx
+++ b/demos/react-supabase-todolist/src/app/views/sql-console/page.tsx
@@ -1,9 +1,11 @@
-import React from 'react';
+import React, { Suspense } from 'react';
import { usePowerSyncWatchedQuery } from '@journeyapps/powersync-react';
-import { Box, Button, Grid, TextField, styled } from '@mui/material';
+import { Box, Button, CircularProgress, Grid, TextField, styled } from '@mui/material';
import { DataGrid } from '@mui/x-data-grid';
import { NavigationPage } from '@/components/navigation/NavigationPage';
+import { ErrorBoundary } from "react-error-boundary";
+
export type LoginFormParams = {
email: string;
password: string;
@@ -14,21 +16,6 @@ const DEFAULT_QUERY = 'SELECT * FROM lists';
export default function SQLConsolePage() {
const inputRef = React.useRef();
const [query, setQuery] = React.useState(DEFAULT_QUERY);
- const querySQLResult = usePowerSyncWatchedQuery(query);
-
- const queryDataGridResult = React.useMemo(() => {
- const firstItem = querySQLResult?.[0];
-
- return {
- columns: firstItem
- ? Object.keys(firstItem).map((field) => ({
- field,
- flex: 1
- }))
- : [],
- rows: querySQLResult
- };
- }, [querySQLResult]);
return (
@@ -64,31 +51,67 @@ export default function SQLConsolePage() {
- {queryDataGridResult ? (
-
- {queryDataGridResult.columns ? (
- ({ ...r, id: r.id ?? index })) ?? []}
- columns={queryDataGridResult.columns}
- initialState={{
- pagination: {
- paginationModel: {
- pageSize: 20
- }
- }
- }}
- pageSizeOptions={[20]}
- disableRowSelectionOnClick
- />
- ) : null}
-
- ) : null}
+ }>
+ {/* Use resetKeys to dismiss the error when changing the query. */}
+
+
+
+
+
);
}
+function SqlConsoleResults(props: { query: string }) {
+ const querySQLResult = usePowerSyncWatchedQuery(props.query);
+
+ const queryDataGridResult = React.useMemo(() => {
+ const firstItem = querySQLResult?.[0];
+
+ return {
+ columns: firstItem
+ ? Object.keys(firstItem).map((field) => ({
+ field,
+ flex: 1
+ }))
+ : [],
+ rows: querySQLResult
+ };
+ }, [querySQLResult]);
+
+ return queryDataGridResult ? (
+
+ {queryDataGridResult.columns ? (
+ ({ ...r, id: r.id ?? index })) ?? []}
+ columns={queryDataGridResult.columns}
+ initialState={{
+ pagination: {
+ paginationModel: {
+ pageSize: 20
+ }
+ }
+ }}
+ pageSizeOptions={[20]}
+ disableRowSelectionOnClick
+ />
+ ) : null}
+
+ ) : null
+}
+
+function fallbackRender(options: { error: any }) {
+ return (
+
+
{options.error.message}
+
+ );
+}
+
+
namespace S {
export const MainContainer = styled(Box)`
padding: 20px;
diff --git a/demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx b/demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx
index c92f52db..851afffb 100644
--- a/demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx
+++ b/demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx
@@ -1,26 +1,27 @@
+import { NavigationPage } from '@/components/navigation/NavigationPage';
import { useSupabase } from '@/components/providers/SystemProvider';
import { TodoItemWidget } from '@/components/widgets/TodoItemWidget';
+import { TodoListsWidget } from '@/components/widgets/TodoListsWidget';
import { LISTS_TABLE, TODOS_TABLE, TodoRecord } from '@/library/powersync/AppSchema';
import { usePowerSync, usePowerSyncWatchedQuery } from '@journeyapps/powersync-react';
import AddIcon from '@mui/icons-material/Add';
import {
Box,
Button,
- CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
+ Grid,
List,
TextField,
Typography,
styled
} from '@mui/material';
import Fab from '@mui/material/Fab';
-import React, { Suspense } from 'react';
+import React from 'react';
import { useParams } from 'react-router-dom';
-import { NavigationPage } from '@/components/navigation/NavigationPage';
/**
* useSearchParams causes the entire element to fall back to client side rendering
@@ -35,7 +36,6 @@ const TodoEditSection = () => {
const [listRecord] = usePowerSyncWatchedQuery<{ name: string }>(`SELECT name FROM ${LISTS_TABLE} WHERE id = ?`, [
listID
]);
-
const todos = usePowerSyncWatchedQuery(
`SELECT * FROM ${TODOS_TABLE} WHERE list_id=? ORDER BY created_at DESC, id`,
[listID]
@@ -104,17 +104,24 @@ const TodoEditSection = () => {
-
- {todos.map((r) => (
- deleteTodo(r.id)}
- isComplete={r.completed == 1}
- toggleCompletion={() => toggleCompletion(r, !r.completed)}
- />
- ))}
-
+
+
+
+
+
+
+ {todos.map((r) => (
+ deleteTodo(r.id)}
+ isComplete={r.completed == 1}
+ toggleCompletion={() => toggleCompletion(r, !r.completed)}
+ />
+ ))}
+
+
+
{/* TODO use a dialog service in future, this is just a simple example app */}