Skip to content

Instantly share code, notes, and snippets.

@mfbx9da4
Last active March 7, 2022 10:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mfbx9da4/c156f690789ab75344d6dd1436336229 to your computer and use it in GitHub Desktop.
Save mfbx9da4/c156f690789ab75344d6dd1436336229 to your computer and use it in GitHub Desktop.
Github issue realm-js flatlist + listener poll for transaction
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