I’m building a React-Native app using Realm. It took a while to land on a pattern that works for my app after reading through the docs and a bunch of the GitHub Issues. I have something that works but I’m still getting random errors that don’t make sense such as the mysterious
Error: The Realm is already in a write transaction
I’m using a singleton Realm in a Context Provider like so (I’ve commented out the realm.close because when React Native reloads after modifying/saving files I see more errors):
const RealmContext = React.createContext<Realm|null>(null);
export const RealmProvider: React.FC = ({children}) => {
const [realm, setRealm] = React.useState<Realm|null>(null);
React.useEffect(() => {
Realm.open(REALM_CONFIG).then((openRealm) => {
console.log(`${moment().format(TIME_FORMAT)} realm opened`);
setRealm(openRealm);
});
return () => {
//realm?.close();
};
}, []);
return (
<>
<RealmContext.Provider value={realm}>
{children}
</RealmContext.Provider>
</>
);
};
export const useRealm = () => React.useContext(RealmContext);
For my listeners I’ve created a few custom hooks that more or less allow me to integrate with React’s useState as shown.
export default function useRealmObjectHook<T>({ source, primaryKey }: RealmObjectParams<T>) {
const realm = useRealm();
const [data, setData] = React.useState<Data<T>>();
const query = React.useMemo(() => {
if (realm && source && primaryKey) {
return realm.objectForPrimaryKey<T>(source, primaryKey);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [source, primaryKey] );
const safeAddListener = React.useCallback((callback:any) => {
if (realm && query) {
if (realm.isInTransaction) {
setTimeout(() => { safeAddListener(callback); }, 50);
console.log(`realm.isInTransaction === true; ${source} ${primaryKey}`);
} else {
query.addListener(callback);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [realm, query]);
React.useEffect(() => {
function handleChange(_object: T, changes: Realm.ObjectChangeSet) {
const { changedProperties, deleted } = changes;
if (changedProperties.length > 0 || deleted) {
//console.log('setting data');
setData({
data: (_object),
a: moment().valueOf(), //note this hack here as described in a GitHub post
});
}
}
if (query) {
//console.log('addListener', query);
setData({
data: query,
a: moment().valueOf(),
});
safeAddListener(handleChange);
}
return () => {
if (query) {
//console.log('removeAllListeners');
query.removeAllListeners();
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [query]);
return data?.data;
}
My app has a Websocket that receives data and writes to the Realm database. All of the writes happen in a separate file and look like this:
export const updateUser = (apiUser: ApiUser) => {
return new Promise((resolve, reject) => {
const realm = new Realm(REALM_CONFIG);
realm.write(() => {
realm.create(User.schema.name, new User(apiUser), Realm.UpdateMode.Modified);
});
resolve({});
});
};
As you can see, when I write to the database, I always ‘instantiate’ a new Realm object and pass in the same REALM_CONFIG that the RealmContext also uses. Per the docs, this is the synchronous way of opening a Realm. To me, opening and writing to the realm in this manner should be completely safe. However as I indicated, I still see error I noted above frequently especially when the app is starting up and I’m creating a bunch of listeners. I gathered from this comment (Realm is already in a write transaction inside live collection listener!? · Issue #1188 · realm/realm-js · GitHub) that I only needed to check IsInTransaction when I do a write transaction while in a listener which I’m not doing. It kind of feels like even though I’m instantiating a new realm when writing that it’s still using the same one. It feels like I’m misunderstanding a fundamental concept. Anyone know what’s going on here?
Thanks