Last active
March 7, 2022 10:12
-
-
Save mfbx9da4/c156f690789ab75344d6dd1436336229 to your computer and use it in GitHub Desktop.
Github issue realm-js flatlist + listener poll for transaction
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 React, { useEffect, useLayoutEffect, useState } from 'react' | |
import { FlatList, SafeAreaView } from 'react-native' | |
import RNFS from 'react-native-fs' | |
import Realm from 'realm' | |
export function App() { | |
const r = useWaitForRealm() | |
const [initialized, setInitialized] = useState(false) | |
useEffect(() => { | |
if (!r) return | |
const asyncEffect = async () => { | |
// Cleanup the db | |
const fooResults = realm.objects<Foo>('foo') | |
realm.write(() => { | |
for (const x of fooResults) { | |
realm.delete(x) | |
} | |
realm.create<Foo>('foo', { id: '-1' }, Realm.UpdateMode.Modified) | |
}) | |
setInitialized(true) | |
} | |
void asyncEffect() | |
}, [r]) | |
if (!initialized) return null | |
return <FooList /> | |
} | |
let i = 0 | |
const sleep = (milliseconds: number) => new Promise(r => setTimeout(r, milliseconds)) | |
function FooList() { | |
const fooResults = useQuery<Foo>(() => realm.objects<Foo>('foo')) | |
useEffect(() => { | |
const asyncEffect = async () => { | |
while (i < 30) { | |
const id = String(i++) | |
console.log('Start write 1, id:', id) | |
await sleep(10) | |
realm.write(() => { | |
realm.create<Foo>('foo', { id }, Realm.UpdateMode.Modified) | |
}) | |
await sleep(0) | |
console.log('start write 2, id:', id) | |
realm.write(() => { | |
realm.create<Foo>('foo', { id }, Realm.UpdateMode.Modified) | |
}) | |
} | |
} | |
asyncEffect().catch(console.error) | |
}, []) | |
return ( | |
<SafeAreaView style={{ margin: 20 }}> | |
<Text>{fooResults?.length}</Text> | |
{/* {fooResults?.map((_, index) => ( | |
<Message index={index} /> | |
))} */} | |
<FlatList | |
inverted | |
data={fooResults} | |
renderItem={x => <Message index={x.index} />} | |
keyExtractor={item => item.id} | |
maintainVisibleContentPosition={{ minIndexForVisible: 0, autoscrollToTopThreshold: 500 }} | |
/> | |
</SafeAreaView> | |
) | |
} | |
function Message({ index }: { index: number }) { | |
console.log('Message', index) | |
const x = useObject<Foo>('foo', '-1') | |
return <Text>index: {index}</Text> | |
} | |
// #region === Setup the Realm instance (start) === | |
// You can skip reading this bit, I've left it here so it can be easily reproduced. | |
const FooSchema: Realm.ObjectSchema = { | |
name: 'foo', | |
primaryKey: 'id', | |
properties: { | |
id: 'string', | |
}, | |
} | |
export let realm: Realm | |
let realmInitializingPromise: Promise<Realm> | undefined | |
export function waitForRealm() { | |
if (realm) return Promise.resolve(realm) | |
if (!realmInitializingPromise) realmInitializingPromise = initRealm() | |
return realmInitializingPromise | |
} | |
async function initRealm() { | |
const path = `${RNFS.CachesDirectoryPath}/example.realm` | |
realm = await Realm.open({ | |
path, | |
schema: [FooSchema], | |
schemaVersion: 0, | |
}) | |
return realm | |
} | |
export function useWaitForRealm() { | |
const [optionalRealm, setRealm] = useState<Realm | undefined>(realm) | |
useEffect(() => { | |
waitForRealm() | |
.then(x => setRealm(x)) | |
.catch(console.error) | |
}, []) | |
return optionalRealm | |
} | |
type Foo = { id: string } | |
function useQuery<T>(query: () => Realm.Results<any>) { | |
const [collection, setCollection] = useState<Realm.Results<T>>(query()) | |
useEffect(() => { | |
let isMounted = true | |
const listenerCallback: Realm.CollectionChangeCallback<T> = (_, changes) => { | |
const { deletions, insertions, newModifications } = changes | |
if (deletions.length > 0 || insertions.length > 0 || newModifications.length > 0) { | |
setCollection(query()) | |
} | |
} | |
if (collection && collection.isValid() && !realm.isClosed) { | |
waitForNoActiveTransaction() | |
.then(() => { | |
if (isMounted) collection.addListener(listenerCallback) | |
}) | |
.catch(error => console.error('Failed to add query listener', error)) | |
} | |
return () => { | |
isMounted = false | |
collection?.removeListener(listenerCallback) | |
} | |
}, [collection]) | |
return collection | |
} | |
function useObject<T>(type: string, primaryKey: string): (T & Realm.Object) | undefined { | |
const [object, setObject] = useState<(T & Realm.Object) | undefined>( | |
realm.objectForPrimaryKey(type, primaryKey) | |
) | |
useEffect(() => { | |
let isMounted = true | |
const listenerCallback: Realm.ObjectChangeCallback = (_, changes) => { | |
if (changes.changedProperties.length > 0) { | |
setObject(realm.objectForPrimaryKey(type, primaryKey)) | |
} else if (changes.deleted) { | |
setObject(undefined) | |
} | |
} | |
if (object !== undefined) { | |
waitForNoActiveTransaction() | |
.then(() => { | |
if (isMounted) object.addListener(listenerCallback) | |
}) | |
.catch(error => console.error('Failed to add listener', error)) | |
} | |
return () => { | |
object?.removeListener(listenerCallback) | |
isMounted = false | |
} | |
}, [object, type, primaryKey]) | |
return object | |
} | |
function waitForNoActiveTransaction() { | |
return pollFor(() => !realm.isInTransaction, { attempts: 100, interval: 10 }) | |
} | |
const isUndefined = (x: unknown): x is undefined => typeof x === 'undefined' | |
/** | |
* If neither timeout nor attempts is provided, defaults to 30 | |
* attempts. | |
* If only timeout is provided, attempts will be infinite. | |
* If only attempts is provided, timeout will be infinite. | |
* If both are provided, both will be used to limit the poll. | |
*/ | |
export async function pollFor<T>( | |
fn: () => Promise<T | undefined> | T | undefined, | |
opts?: { | |
/** Defaults to 0 - in milliseconds */ | |
interval?: number | |
/** In milliseconds */ | |
timeout?: number | |
attempts?: number | |
} | |
) { | |
let { interval = 0, timeout, attempts } = opts || {} // eslint-disable-line | |
if (!isUndefined(timeout) && isUndefined(attempts)) attempts = Infinity | |
attempts = isUndefined(attempts) ? 30 : attempts | |
timeout = isUndefined(timeout) ? Infinity : timeout | |
const start = Date.now() | |
for (let i = 1; i < attempts + 1; i++) { | |
const result = await fn() | |
if (result !== undefined) return result | |
if (i > attempts) return | |
if (Date.now() - start > timeout) return | |
await sleep(interval) | |
} | |
} | |
export function useObjectOld<T>(type: string, primaryKey: string): (T & Realm.Object) | undefined { | |
const [object, setObject] = useState<(T & Realm.Object) | undefined>( | |
realm.objectForPrimaryKey(type, primaryKey) | |
) | |
useEffect(() => { | |
const listenerCallback: Realm.ObjectChangeCallback = (_, changes) => { | |
if (changes.changedProperties.length > 0) { | |
setObject(realm.objectForPrimaryKey(type, primaryKey)) | |
} else if (changes.deleted) { | |
setObject(undefined) | |
} | |
} | |
if (object !== undefined) { | |
object.addListener(listenerCallback) | |
} | |
return () => { | |
object?.removeListener(listenerCallback) | |
} | |
}, [object, type, primaryKey]) | |
return object | |
} | |
export function useQueryOld<T>(query: () => Realm.Results<any>) { | |
const [collection, setCollection] = useState<Realm.Results<T>>(query()) | |
useEffect(() => { | |
const listenerCallback: Realm.CollectionChangeCallback<T> = (_, changes) => { | |
const { deletions, insertions, newModifications } = changes | |
if (deletions.length > 0 || insertions.length > 0 || newModifications.length > 0) { | |
setCollection(query()) | |
} | |
} | |
if (collection && collection.isValid() && !realm.isClosed) | |
collection.addListener(listenerCallback) | |
return () => { | |
collection?.removeListener(listenerCallback) | |
} | |
}, [collection]) | |
return collection | |
} | |
// #endregion === Setup the Realm instance (end) === |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment