Last active
February 21, 2025 23:29
-
-
Save jamesgpearce/9f505e31bea42ad8e079d31a3356b107 to your computer and use it in GitHub Desktop.
Modified version of TinyBase Expo example to demonstrate synchronization. This updates https://github.com/expo/examples/tree/master/with-tinybase
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useState } from 'react'; | |
import * as SQLite from 'expo-sqlite'; | |
import { | |
FlatList, | |
Platform, | |
StyleSheet, | |
Text, | |
TextInput, | |
TouchableOpacity, | |
} from 'react-native'; | |
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; | |
import { createMergeableStore, getUniqueId } from 'tinybase'; | |
import { createLocalPersister } from 'tinybase/persisters/persister-browser'; | |
import { createExpoSqlitePersister } from 'tinybase/persisters/persister-expo-sqlite'; | |
import { createWsSynchronizer } from 'tinybase/synchronizers/synchronizer-ws-client'; | |
import { | |
Provider, | |
useAddRowCallback, | |
useCreateMergeableStore, | |
useCreatePersister, | |
useCreateSynchronizer, | |
useDelTableCallback, | |
useHasTable, | |
useRow, | |
useSetCellCallback, | |
useSortedRowIds, | |
useStore, | |
} from 'tinybase/ui-react'; | |
// The TinyBase table contains the todos, with 'text' and 'done' cells. | |
const TODO_TABLE = 'todo'; | |
const TEXT_CELL = 'text'; | |
const DONE_CELL = 'done'; | |
// Emojis to decorate each todo. | |
const NOT_DONE_ICON = String.fromCodePoint('0x1F7E0'); | |
const DONE_ICON = String.fromCodePoint('0x2705'); | |
// The text input component to add a new todo. | |
const NewTodo = () => { | |
const [text, setText] = useState(''); | |
const handleSubmitEditing = useAddRowCallback( | |
TODO_TABLE, | |
({ nativeEvent: { text } }) => { | |
setText(''); | |
return { [TEXT_CELL]: text, [DONE_CELL]: false }; | |
} | |
); | |
return ( | |
<TextInput | |
value={text} | |
onChangeText={(text) => setText(text)} | |
onSubmitEditing={handleSubmitEditing} | |
placeholder='What do you want to do today?' | |
style={styles.input} | |
/> | |
); | |
}; | |
// A single todo component, either 'not done' or 'done': press to toggle. | |
const Todo = ({ id }) => { | |
const { text, done } = useRow(TODO_TABLE, id); | |
const handlePress = useSetCellCallback( | |
TODO_TABLE, | |
id, | |
DONE_CELL, | |
() => (done) => !done | |
); | |
return ( | |
<TouchableOpacity | |
key={id} | |
onPress={handlePress} | |
style={[styles.todo, done ? styles.done : null]} | |
> | |
<Text style={styles.todoText}> | |
{done ? DONE_ICON : NOT_DONE_ICON} {text} | |
</Text> | |
</TouchableOpacity> | |
); | |
}; | |
// A list component to show all the todos. | |
const Todos = () => { | |
const renderItem = ({ item: id }) => <Todo id={id} />; | |
return ( | |
<FlatList | |
data={useSortedRowIds(TODO_TABLE, DONE_CELL)} | |
renderItem={renderItem} | |
style={styles.todos} | |
/> | |
); | |
}; | |
const Share = () => { | |
const [roomId, setRoomId] = useState(); | |
const createNewRoom = useCallback(() => { | |
if (!roomId) { | |
setRoomId(getUniqueId()); | |
} | |
}, [roomId]); | |
const store = useStore(); | |
useCreateSynchronizer( | |
store, | |
async (store) => { | |
if (roomId) { | |
const synchronizer = await createWsSynchronizer( | |
store, | |
new WebSocket('wss://todo.demo.tinybase.org/' + roomId) | |
); | |
return await synchronizer.startSync(); | |
} | |
}, | |
[roomId] | |
); | |
return ( | |
<> | |
<Text>Share to:</Text> | |
<TextInput | |
value={roomId} | |
onFocus={createNewRoom} | |
onChangeText={(roomId) => setRoomId(roomId)} | |
placeholder='Room Id' | |
style={styles.input} | |
/> | |
</> | |
); | |
}; | |
// A button component to delete all the todos, only shows when there are some. | |
const ClearTodos = () => { | |
const handlePress = useDelTableCallback(TODO_TABLE); | |
return useHasTable(TODO_TABLE) ? ( | |
<TouchableOpacity onPress={handlePress}> | |
<Text style={styles.clearTodos}>Clear all</Text> | |
</TouchableOpacity> | |
) : null; | |
}; | |
// The main app. | |
const App = () => { | |
// Initialize the (memoized) TinyBase store and persist it. | |
const store = useCreateMergeableStore(createMergeableStore); | |
useAndStartPersister(store); | |
return ( | |
// Wrap the app in TinyBase context, so the store is default throughout and | |
// a SafeAreaProvider/SafeAreaView so it fits the screen. | |
<Provider store={store}> | |
<SafeAreaProvider> | |
<SafeAreaView style={styles.container}> | |
<Text style={styles.heading}>TinyBase Example</Text> | |
<NewTodo /> | |
<Todos /> | |
<ClearTodos /> | |
<Share /> | |
</SafeAreaView> | |
</SafeAreaProvider> | |
</Provider> | |
); | |
}; | |
const useAndStartPersister = (store) => | |
// Persist store to Expo SQLite or local storage; load once, then auto-save. | |
useCreatePersister( | |
store, | |
(store) => | |
Platform.OS === 'web' | |
? createLocalPersister(store, 'todos') | |
: createExpoSqlitePersister(store, SQLite.openDatabaseSync('todos.db')), | |
[], | |
(persister) => persister.load().then(persister.startAutoSave) | |
); | |
// Styles for the app. | |
const styles = StyleSheet.create({ | |
container: { | |
backgroundColor: '#fff', | |
flex: 1, | |
margin: 16, | |
}, | |
heading: { | |
fontSize: 24, | |
fontWeight: 'bold', | |
textAlign: 'center', | |
}, | |
input: { | |
borderColor: '#999', | |
borderRadius: 8, | |
borderWidth: 2, | |
flex: 0, | |
height: 64, | |
marginTop: 16, | |
padding: 16, | |
fontSize: 20, | |
}, | |
todos: { | |
flex: 1, | |
marginTop: 16, | |
}, | |
todo: { | |
borderRadius: 8, | |
marginBottom: 16, | |
padding: 16, | |
backgroundColor: '#ffd', | |
}, | |
done: { | |
backgroundColor: '#dfd', | |
}, | |
todoText: { | |
fontSize: 20, | |
}, | |
clearTodos: { | |
margin: 16, | |
flex: 0, | |
textAlign: 'center', | |
fontSize: 16, | |
}, | |
}); | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment