Using Hooks
A separate powersync-react
package is available containing React hooks for PowerSync:
See its README for example code.
Using transactions to group changes
Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception).
PowerSyncDatabase.writeTransaction(callback) automatically commits changes after the transaction callback is completed if tx.rollback() has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back.
// ListsWidget.jsx
import {Alert, Button, FlatList, Text, View} from 'react-native';
export const ListsWidget = () => {
// Populate lists with one of methods listed above
const [lists, setLists] = React.useState([]);
return (
<View>
<FlatList
data={lists.map(list => ({key: list.id, ...list}))}
renderItem={({item}) => (<View>
<Text>{item.name}</Text>
<Button
title="Delete"
onPress={async () => {
try {
await PowerSync.writeTransaction(async (tx) => {
// Delete the main list
await tx.execute(`DELETE FROM lists WHERE id = ?`, [item.id]);
// Delete any children of the list
await tx.execute(`DELETE FROM todos WHERE list_id = ?`, [item.id]);
// Transactions are automatically committed at the end of execution
// Transactions are automatically rolled back if an exception occurred
})
// Watched queries should automatically reload after mutation
} catch (ex) {
Alert.alert('Error', ex.message)
}
}}
/>
</View>)}
/>
<Button
title="Create List"
color="#841584"
onPress={async () => {
try {
await PowerSync.execute('INSERT INTO lists (id, created_at, name, owner_id) VALUES (uuid(), datetime(), ?, ?) RETURNING *', [
'A list name',
"[The user's uuid]"
])
// Watched queries should automatically reload after mutation
} catch (ex) {
Alert.alert('Error', ex.message)
}
}}
/>
</View>
)
}
Also see PowerSyncDatabase.readTransaction(callback).
Subscribe to changes in data
Use PowerSyncDatabase.watch to watch for changes in source tables.
The watch
method can be used with a AsyncIterable
signature as follows:
async *attachmentIds(): AsyncIterable<string[]> {
for await (const result of this.powersync.watch(
`SELECT photo_id as id FROM ${TODO_TABLE} WHERE photo_id IS NOT NULL`,
[]
)) {
yield result.rows?._array.map((r) => r.id) ?? [];
}
}
As of version 1.3.3 of the SDK, the watch
method can also be used with a callback:
attachmentIds(onResult: (ids: string[]) => void): void {
this.powersync.watch(
`SELECT photo_id as id FROM ${TODO_TABLE} WHERE photo_id IS NOT NULL`,
[],
{
onResult: (result) => {
onResult(result.rows?._array.map((r) => r.id) ?? []);
}
}
);
}
Insert, update, and delete data in the local database
Use PowerSyncDatabase.execute to run INSERT, UPDATE or DELETE queries.
const handleButtonClick = async () => {
await db.execute(
'INSERT INTO customers(id, name, email) VALUES(uuid(), ?, ?)',
['Fred', 'fred@example.org']
);
};
return (
<button onClick={handleButtonClick} title="+">
<span>+</span>
<i className="material-icons">add</i>
</button>
);
Send changes in local data to your backend service
Override uploadData to send local updates to your backend service.
// Implement the uploadData method in your backend connector
async function uploadData(database) {
const batch = await database.getCrudBatch();
if (batch === null) return;
for (const op of batch.crud) {
switch (op.op) {
case 'put':
// Send the data to your backend service
// replace `_myApi` with your own API client or service
await _myApi.put(op.table, op.opData);
break;
default:
// TODO: implement the other operations (patch, delete)
break;
}
}
await batch.complete();
}
Use PowerSyncDatabase.connected and register an event listener with PowerSyncDatabase.registerListener to listen for status changes to your PowerSync instance.
// Example of using connected status to show online or offline
// Tap into connected
const [connected, setConnected] = React.useState(powersync.connected);
React.useEffect(() => {
// Register listener for changes made to the powersync status
return powersync.registerListener({
statusChanged: (status) => {
setConnected(status.connected);
}
});
}, [powersync]);
// Icon to show connected or not connected to powersync
// as well as the last synced time
<Icon
name={connected ? 'wifi' : 'wifi-off'}
type="material-community"
color="black"
size={20}
style={{ padding: 5 }}
onPress={() => {
Alert.alert(
'Status',
`${connected ? 'Connected' : 'Disconnected'}. \nLast Synced at ${powersync.currentStatus?.lastSyncedAt.toISOString() ?? '-'
}\nVersion: ${powersync.sdkVersion}`
);
}}
/>;
Wait for the initial sync to complete
Use the hasSynced property (available since version 1.4.1 of the SDK) and register an event listener with PowerSyncDatabase.registerListener to indicate to the user whether the initial sync is in progress.
// Example of using hasSynced to show whether the first sync has completed
// Tap into hasSynced
const [hasSynced, setHasSynced] = React.useState(powerSync.currentStatus?.hasSynced || false);
React.useEffect(() => {
// Register listener for changes made to the powersync status
return powerSync.registerListener({
statusChanged: (status) => {
setHasSynced(!!status.hasSynced);
}
});
}, [powerSync]);
return <Text>{hasSynced ? 'Initial sync completed!' : 'Busy with initial sync...'}</Text>;
For async use cases, see PowerSyncDatabase.waitForFirstSync, which returns a promise that resolves once the first full sync has completed (it queries the internal SQL ps_buckets table to determine if data has been synced).
Report sync download progress
You can show users a progress bar when data downloads using the downloadProgress
property from the SyncStatus class. This is especially useful for long-running initial syncs. downloadProgress.downloadedFraction
gives you a value from 0.0 to 1.0 representing the total sync progress.
Example:
import { useStatus } from '@powersync/react';
import { FC, ReactNode } from 'react';
import { View } from 'react-native';
import { Text, LinearProgress } from '@rneui/themed';
export const SyncProgressBar: FC<{ priority?: number }> = ({ priority }) => {
const status = useStatus();
const progressUntilNextSync = status.downloadProgress;
const progress = priority == null ? progressUntilNextSync : progressUntilNextSync?.untilPriority(priority);
if (progress == null) {
return <></>;
}
return (
<View>
<LinearProgress variant="determinate" value={progress.downloadedFraction * 100} />
{progress.downloadedOperations == progress.totalOperations ? (
<Text>Applying server-side changes</Text>
) : (
<Text>
Downloaded {progress.downloadedOperations} out of {progress.totalOperations}.
</Text>
)}
</View>
);
};
Also see:
Responses are generated using AI and may contain mistakes.