1+ import React , { useEffect , useState , useCallback } from 'react' ;
2+ import {
3+ ScrollView ,
4+ StatusBar ,
5+ StyleSheet ,
6+ Text ,
7+ useColorScheme ,
8+ View ,
9+ TouchableOpacity ,
10+ ActivityIndicator
11+ } from 'react-native' ;
12+ import { SafeAreaProvider , SafeAreaView } from 'react-native-safe-area-context' ;
13+ import {
14+ column ,
15+ Table ,
16+ Schema ,
17+ type PowerSyncBackendConnector ,
18+ PowerSyncDatabase ,
19+ createConsoleLogger ,
20+ LogLevels
21+ } from '@powersync/react-native' ;
22+
23+ const Colors = {
24+ primary : '#0a7ea4' ,
25+ white : '#ffffff' ,
26+ black : '#000000' ,
27+ light : '#f5f5f5' ,
28+ dark : '#333333' ,
29+ lighter : '#f3f4f6' ,
30+ darker : '#1a1a1a'
31+ } ;
32+
33+ const RANDOM_NAMES = [ 'Alex' , 'Jordan' , 'Sam' , 'Casey' , 'Riley' , 'Morgan' , 'Quinn' , 'Avery' , 'Taylor' , 'Jamie' ] ;
34+
35+ /**
36+ * A placeholder connector which doesn't do anything but used to confirm connect can run.
37+ */
38+ class DummyConnector implements PowerSyncBackendConnector {
39+ async fetchCredentials ( ) {
40+ return {
41+ endpoint : '' ,
42+ token : ''
43+ } ;
44+ }
45+
46+ async uploadData ( ) { }
47+ }
48+
49+ const customersTable = new Table ( { name : column . text } ) ;
50+ const schema = new Schema ( { customers : customersTable } ) ;
51+
52+ let powerSync : PowerSyncDatabase | null = null ;
53+
54+ const setupDatabase = async ( ) : Promise < PowerSyncDatabase > => {
55+ if ( powerSync ) return powerSync ;
56+
57+ powerSync = new PowerSyncDatabase ( {
58+ schema,
59+ database : {
60+ dbFilename : 'powersync.db'
61+ } ,
62+ logger : createConsoleLogger ( { minLevel : LogLevels . debug } )
63+ } ) ;
64+
65+ await powerSync . init ( ) ;
66+ return powerSync ;
67+ } ;
68+
69+ type Customer = { id : string ; name : string } ;
70+
71+ function App ( ) : React . JSX . Element {
72+ const isDarkMode = useColorScheme ( ) === 'dark' ;
73+ const [ customers , setCustomers ] = useState < Customer [ ] > ( [ ] ) ;
74+ const [ loading , setLoading ] = useState ( true ) ;
75+ const [ adding , setAdding ] = useState ( false ) ;
76+ const [ deletingId , setDeletingId ] = useState < string | null > ( null ) ;
77+
78+ const backgroundStyle = {
79+ backgroundColor : isDarkMode ? Colors . darker : Colors . lighter
80+ } ;
81+ const cardBg = isDarkMode ? Colors . darker : Colors . white ;
82+ const textColor = isDarkMode ? Colors . white : Colors . black ;
83+ const mutedColor = isDarkMode ? Colors . light : Colors . dark ;
84+
85+ const loadCustomers = useCallback ( async ( ) => {
86+ if ( ! powerSync ) return ;
87+ try {
88+ const result = await powerSync . getAll < Customer > ( 'SELECT id, name FROM customers ORDER BY name' ) ;
89+ setCustomers ( Array . isArray ( result ) ? result : [ ] ) ;
90+ } catch ( e ) {
91+ console . error ( 'Failed to load customers:' , e ) ;
92+ }
93+ } , [ ] ) ;
94+
95+ useEffect ( ( ) => {
96+ let mounted = true ;
97+
98+ const init = async ( ) => {
99+ try {
100+ const db = await setupDatabase ( ) ;
101+ //await db.connect(new DummyConnector());
102+ if ( mounted ) await loadCustomers ( ) ;
103+ } catch ( error ) {
104+ console . error ( 'Database initialization error:' , error ) ;
105+ } finally {
106+ if ( mounted ) setLoading ( false ) ;
107+ }
108+ } ;
109+
110+ init ( ) ;
111+ return ( ) => {
112+ mounted = false ;
113+ } ;
114+ } , [ loadCustomers ] ) ;
115+
116+ const addRandomCustomer = async ( ) => {
117+ if ( ! powerSync || adding ) return ;
118+ setAdding ( true ) ;
119+ try {
120+ const name = RANDOM_NAMES [ Math . floor ( Math . random ( ) * RANDOM_NAMES . length ) ] ;
121+ await powerSync . execute ( 'INSERT INTO customers(id, name) VALUES(uuid(), ?)' , [ name ] ) ;
122+ await loadCustomers ( ) ;
123+ } catch ( e ) {
124+ console . error ( 'Failed to add customer:' , e ) ;
125+ } finally {
126+ setAdding ( false ) ;
127+ }
128+ } ;
129+
130+ const deleteCustomer = async ( id : string ) => {
131+ if ( ! powerSync || deletingId ) return ;
132+ setDeletingId ( id ) ;
133+ try {
134+ await powerSync . execute ( 'DELETE FROM customers WHERE id = ?' , [ id ] ) ;
135+ await loadCustomers ( ) ;
136+ } catch ( e ) {
137+ console . error ( 'Failed to delete customer:' , e ) ;
138+ } finally {
139+ setDeletingId ( null ) ;
140+ }
141+ } ;
142+
143+ return (
144+ < SafeAreaProvider >
145+ < SafeAreaView style = { [ backgroundStyle , styles . container ] } >
146+ < StatusBar
147+ barStyle = { isDarkMode ? 'light-content' : 'dark-content' }
148+ />
149+ < View style = { styles . header } >
150+ < Text style = { [ styles . headerTitle , { color : textColor } ] } > Customers</ Text >
151+ < Text style = { [ styles . headerSubtitle , { color : mutedColor } ] } > PowerSync on React Native</ Text >
152+ </ View >
153+
154+ { loading ? (
155+ < View style = { styles . centered } >
156+ < ActivityIndicator size = "large" color = { Colors . primary } />
157+ < Text style = { [ styles . mutedText , { color : mutedColor } ] } > Loading…</ Text >
158+ </ View >
159+ ) : (
160+ < ScrollView
161+ contentInsetAdjustmentBehavior = "automatic"
162+ style = { backgroundStyle }
163+ contentContainerStyle = { styles . scrollContent } >
164+ < View style = { [ styles . card , { backgroundColor : cardBg } ] } >
165+ { customers . length === 0 ? (
166+ < Text style = { [ styles . emptyText , { color : mutedColor } ] } > No customers yet.</ Text >
167+ ) : (
168+ customers . map ( ( c ) => (
169+ < View key = { c . id } style = { styles . row } >
170+ < Text style = { [ styles . customerName , { color : textColor } ] } > { c . name } </ Text >
171+ < TouchableOpacity
172+ style = { styles . deleteButton }
173+ onPress = { ( ) => deleteCustomer ( c . id ) }
174+ disabled = { deletingId !== null } >
175+ { deletingId === c . id ? (
176+ < ActivityIndicator size = "small" color = { Colors . primary } />
177+ ) : (
178+ < Text style = { styles . deleteButtonText } > Delete</ Text >
179+ ) }
180+ </ TouchableOpacity >
181+ </ View >
182+ ) )
183+ ) }
184+ </ View >
185+
186+ < TouchableOpacity
187+ style = { [ styles . button , adding && styles . buttonDisabled ] }
188+ onPress = { addRandomCustomer }
189+ disabled = { adding } >
190+ { adding ? (
191+ < ActivityIndicator size = "small" color = { Colors . white } />
192+ ) : (
193+ < Text style = { styles . buttonText } > Add random customer</ Text >
194+ ) }
195+ </ TouchableOpacity >
196+ </ ScrollView >
197+ ) }
198+ </ SafeAreaView >
199+ </ SafeAreaProvider >
200+ ) ;
201+ }
202+
203+ const styles = StyleSheet . create ( {
204+ container : {
205+ flex : 1
206+ } ,
207+ header : {
208+ paddingVertical : 20 ,
209+ paddingHorizontal : 24
210+ } ,
211+ headerTitle : {
212+ fontSize : 28 ,
213+ fontWeight : '700'
214+ } ,
215+ headerSubtitle : {
216+ fontSize : 14 ,
217+ marginTop : 4
218+ } ,
219+ scrollContent : {
220+ padding : 24 ,
221+ paddingBottom : 48
222+ } ,
223+ card : {
224+ borderRadius : 12 ,
225+ padding : 16 ,
226+ marginBottom : 24
227+ } ,
228+ row : {
229+ flexDirection : 'row' ,
230+ alignItems : 'center' ,
231+ justifyContent : 'space-between' ,
232+ paddingVertical : 12 ,
233+ borderBottomWidth : StyleSheet . hairlineWidth ,
234+ borderBottomColor : 'rgba(0,0,0,0.08)'
235+ } ,
236+ customerName : {
237+ fontSize : 18 ,
238+ flex : 1
239+ } ,
240+ deleteButton : {
241+ paddingVertical : 8 ,
242+ paddingHorizontal : 12 ,
243+ minWidth : 72 ,
244+ alignItems : 'center'
245+ } ,
246+ deleteButtonText : {
247+ color : '#c53030' ,
248+ fontSize : 15 ,
249+ fontWeight : '500'
250+ } ,
251+ emptyText : {
252+ fontSize : 16 ,
253+ textAlign : 'center' ,
254+ paddingVertical : 24
255+ } ,
256+ button : {
257+ backgroundColor : Colors . primary ,
258+ paddingVertical : 16 ,
259+ paddingHorizontal : 24 ,
260+ borderRadius : 12 ,
261+ alignItems : 'center' ,
262+ minHeight : 52 ,
263+ justifyContent : 'center'
264+ } ,
265+ buttonDisabled : {
266+ opacity : 0.7
267+ } ,
268+ buttonText : {
269+ color : Colors . white ,
270+ fontSize : 17 ,
271+ fontWeight : '600'
272+ } ,
273+ centered : {
274+ flex : 1 ,
275+ justifyContent : 'center' ,
276+ alignItems : 'center' ,
277+ gap : 12
278+ } ,
279+ mutedText : {
280+ fontSize : 16
281+ }
282+ } ) ;
283+
284+ export default App ;
0 commit comments