Skip to content

Commit e8f8085

Browse files
committed
Added readme entry for useSuspenseQuery.
1 parent 7f6f088 commit e8f8085

File tree

2 files changed

+156
-4
lines changed

2 files changed

+156
-4
lines changed

packages/react/README.md

Lines changed: 155 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const TodoListDisplay = () => {
4444
}
4545
```
4646

47-
### Accessing PowerSync Status
47+
## Accessing PowerSync Status
4848

4949
The provided PowerSync client status is available with the `useStatus` hook.
5050

@@ -63,7 +63,7 @@ const Component = () => {
6363
};
6464
```
6565

66-
### Queries
66+
## Queries
6767

6868
Queries will automatically update when a dependant table is updated unless you set the `runQueryOnce` flag. You are also able to use a compilable query (e.g. [Kysely queries](https://github.com/powersync-ja/powersync-js/tree/main/packages/kysely-driver)) as a query argument in place of a SQL statement string.
6969

@@ -82,7 +82,7 @@ export const TodoListDisplay = () => {
8282
}
8383
```
8484

85-
#### Query Loading
85+
### Query Loading
8686

8787
The response from `useQuery` includes the `isLoading` and `isFetching` properties, which indicate the current state of data retrieval. This can be used to show loading spinners or conditional widgets.
8888

@@ -116,3 +116,155 @@ export const TodoListsDisplayDemo = () => {
116116
};
117117

118118
```
119+
120+
### Suspense
121+
122+
The `useSuspenseQuery` hook is available to handle the loading/fetching state through suspense.
123+
Unlike `useQuery`, the hook doesn't return `isLoading` or `isFetching` for the loading states nor `error` for the error state. These should be handled with variants of `<Suspense>` and `<ErrorBoundary>` respectively.
124+
125+
```JSX
126+
// TodoListDisplaySuspense.jsx
127+
import { ErrorBoundary } from 'react-error-boundary';
128+
import { Suspense } from 'react';
129+
import { useSuspenseQuery } from '@powersync/react';
130+
131+
const TodoListContent = () => {
132+
const { data: todoLists } = useSuspenseQuery("SELECT * FROM lists");
133+
134+
return (
135+
<ul>
136+
{todoLists.map((list) => (
137+
<li key={list.id}>{list.name}</li>
138+
))}
139+
</ul>
140+
);
141+
};
142+
143+
144+
export const TodoListDisplaySuspense = () => {
145+
return (
146+
<ErrorBoundary fallback={<div>Something went wrong</div>}>
147+
<Suspense fallback={<div>Loading todo lists...</div>}>
148+
<TodoListContent />
149+
</Suspense>
150+
</ErrorBoundary>
151+
);
152+
};
153+
```
154+
155+
#### Blocking navigation on Suspense
156+
157+
When you provide a suspense fallback, suspending components will cause the fallback to render. Alternatively, React's [startTransition](https://react.dev/reference/react/startTransition) allows navigation to be blocked until the suspending components have completed, preventing the fallback from displaying. This behavior can be facilitated by your router — for example, react-router supports this with its [startTransition flag](https://reactrouter.com/en/main/upgrading/future#v7_starttransition).
158+
159+
> Note: In this example, the `<Suspense>` boundary is intentionally omitted to delegate the handling of the suspending state to the router.
160+
161+
```JSX
162+
// routerAndLists.jsx
163+
import { RouterProvider } from 'react-router-dom';
164+
import { ErrorBoundary } from 'react-error-boundary';
165+
import { useSuspenseQuery } from '@powersync/react';
166+
167+
export const Index() {
168+
return <RouterProvider router={router} future={{v7_startTransition: true}} />
169+
}
170+
171+
const TodoListContent = () => {
172+
const { data: todoLists } = useSuspenseQuery("SELECT * FROM lists");
173+
174+
return (
175+
<ul>
176+
{todoLists.map((list) => (
177+
<li key={list.id}>{list.name}</li>
178+
))}
179+
</ul>
180+
);
181+
};
182+
183+
184+
export const TodoListsPage = () => {
185+
return (
186+
<ErrorBoundary fallback={<div>Something went wrong</div>}>
187+
<TodoListContent />
188+
</ErrorBoundary>
189+
);
190+
};
191+
```
192+
193+
#### Managing Suspense When Updating `useSuspenseQuery` Parameters
194+
195+
When data in dependent tables changes, `useSuspenseQuery` automatically updates without suspending. However, changing the query parameters causes the hook to restart and enter a suspending state again, which triggers the suspense fallback. To prevent this and keep displaying the stale data until the new data is loaded, wrap the parameter changes in React's [startTransition](https://react.dev/reference/react/startTransition) or use [useDeferredValue](https://react.dev/reference/react/useDeferredValue).
196+
197+
```JSX
198+
// TodoListDisplaySuspenseTransition.jsx
199+
import { ErrorBoundary } from 'react-error-boundary';
200+
import React, { Suspense } from 'react';
201+
import { useSuspenseQuery } from '@powersync/react';
202+
203+
const TodoListContent = () => {
204+
const [query, setQuery] = React.useState('SELECT * FROM lists');
205+
const { data: todoLists } = useSuspenseQuery(query);
206+
207+
return (
208+
<div>
209+
<button
210+
onClick={() => {
211+
React.startTransition(() => setQuery('SELECT * from lists limit 1'));
212+
}}>
213+
Update
214+
</button>
215+
<ul>
216+
{todoLists.map((list) => (
217+
<li key={list.id}>{list.name}</li>
218+
))}
219+
</ul>
220+
</div>
221+
);
222+
};
223+
224+
export const TodoListDisplaySuspense = () => {
225+
return (
226+
<ErrorBoundary fallback={<div>Something went wrong</div>}>
227+
<Suspense fallback={<div>Loading todo lists...</div>}>
228+
<TodoListContent />
229+
</Suspense>
230+
</ErrorBoundary>
231+
);
232+
};
233+
```
234+
235+
and
236+
237+
```JSX
238+
// TodoListDisplaySuspenseDeferred.jsx
239+
import { ErrorBoundary } from 'react-error-boundary';
240+
import React, { Suspense } from 'react';
241+
import { useSuspenseQuery } from '@powersync/react';
242+
243+
const TodoListContent = () => {
244+
const [query, setQuery] = React.useState('SELECT * FROM lists');
245+
const deferredQueryQuery = React.useDeferredValue(query);
246+
247+
const { data: todoLists } = useSuspenseQuery(deferredQueryQuery);
248+
249+
return (
250+
<div>
251+
<button onClick={() => setQuery('SELECT * from lists limit 1')}>Update</button>
252+
<ul>
253+
{todoLists.map((list) => (
254+
<li key={list.id}>{list.name}</li>
255+
))}
256+
</ul>
257+
</div>
258+
);
259+
};
260+
261+
export const TodoListDisplaySuspense = () => {
262+
return (
263+
<ErrorBoundary fallback={<div>Something went wrong</div>}>
264+
<Suspense fallback={<div>Loading todo lists...</div>}>
265+
<TodoListContent />
266+
</Suspense>
267+
</ErrorBoundary>
268+
);
269+
};
270+
```

packages/react/src/WatchedQuery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class WatchedQuery {
5555
if (this.isReady || this.controller == null) {
5656
release();
5757
} else {
58-
// If the query is is taking long, keep the temporay hold.
58+
// If the query is taking long, keep the temporary hold.
5959
timeout = setTimeout(release, 5_000);
6060
}
6161
};

0 commit comments

Comments
 (0)