Socket
Socket
Sign inDemoInstall

@realm/react

Package Overview
Dependencies
4
Maintainers
7
Versions
19
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.4.1 to 0.4.3

4

dist/__tests__/RealmProvider.test.js

@@ -162,3 +162,5 @@ ////////////////////////////////////////////////////////////////////////////

});
describe("initially renders a fallback, until realm exists", () => {
// TODO: Now that local realm is immediately set, the fallback never renders.
// We need to test synced realm in order to produce the fallback
describe.skip("initially renders a fallback, until realm exists", () => {
it("as a component", async () => {

@@ -165,0 +167,0 @@ const App = () => {

import Realm from "realm";
export declare class ListItem extends Realm.Object {
id: number;
id: Realm.BSON.ObjectId;
name: string;

@@ -9,3 +9,3 @@ lists: Realm.List<List>;

export declare class List extends Realm.Object {
id: number;
id: Realm.BSON.ObjectId;
title: string;

@@ -12,0 +12,0 @@ items: Realm.List<ListItem>;

@@ -19,3 +19,3 @@ ////////////////////////////////////////////////////////////////////////////

import { fireEvent, render, waitFor, act } from "@testing-library/react-native";
import React, { useCallback, useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { TextInput, Text, TouchableHighlight, View, FlatList } from "react-native";

@@ -31,3 +31,3 @@ import Realm from "realm";

properties: {
id: "int",
id: "objectId",
name: "string",

@@ -52,3 +52,3 @@ lists: {

properties: {
id: "int",
id: "objectId",
title: "string",

@@ -76,4 +76,9 @@ items: "ListItem[]",

const listRenderCounter = jest.fn();
const objectChangeCounter = jest.fn();
const listChangeCounter = jest.fn();
let testRealm = new Realm(configuration);
const testCollection = [...new Array(100)].map((_, index) => ({ id: index, name: `${index}` }));
const testCollection = [...new Array(100)].map(() => {
const id = new Realm.BSON.ObjectId();
return { id, name: id.toHexString() };
});
const useRealm = () => {

@@ -85,6 +90,10 @@ testRealm = new Realm(configuration);

const useObject = createUseObject(useRealm);
const App = () => {
// setting renderItems to false will invoke `useObject` but not actual display the data
// this is to test that the changes to the object is still trigger a rerender, even when not displayed
// see https://github.com/realm/realm-js/issues/5185
const App = ({ renderItems = true, targetPrimaryKey = parentObjectId }) => {
return (React.createElement(SetupComponent, null,
React.createElement(TestComponent, { testID: "testContainer" })));
React.createElement(TestComponent, { testID: "testContainer", renderItems: renderItems, targetPrimaryKey: targetPrimaryKey })));
};
const parentObjectId = new Realm.BSON.ObjectId();
const SetupComponent = ({ children }) => {

@@ -101,3 +110,2 @@ const realm = useRealm();

realm.deleteAll();
realm.create(List, { id: 1, title: "List", items: testCollection });
});

@@ -114,6 +122,7 @@ setSetupComplete(true);

const realm = useRealm();
return (React.createElement(View, { testID: `result${item.id}` },
React.createElement(View, { testID: `name${item.id}` },
const idString = item.id.toHexString();
return (React.createElement(View, { testID: `result${idString}` },
React.createElement(View, { testID: `name${idString}` },
React.createElement(Text, null, item.name)),
React.createElement(TextInput, { testID: `input${item.id}`, value: item.name, onChangeText: (text) => {
React.createElement(TextInput, { testID: `input${idString}`, value: item.name, onChangeText: (text) => {
realm.write(() => {

@@ -123,3 +132,3 @@ item.name = text;

} }),
React.createElement(TouchableHighlight, { testID: `deleteButton${item.id}`, onPress: () => {
React.createElement(TouchableHighlight, { testID: `deleteButton${idString}`, onPress: () => {
realm.write(() => {

@@ -131,18 +140,38 @@ realm.delete(item);

});
const TestComponent = ({ testID }) => {
const list = useObject(List, 1);
const keyExtractor = (item) => `${item.id}`;
const renderItem = ({ item }) => React.createElement(Item, { item: item });
const ItemList = React.memo(({ list }) => {
const [counter, setCounter] = useState(0);
// This useEffect is to test that the list object reference is not changing when
// the component is re-rendered.
useEffect(() => {
listChangeCounter();
}, [list]);
return (React.createElement(React.Fragment, null,
React.createElement(FlatList, { data: list, keyExtractor: keyExtractor, renderItem: renderItem }),
React.createElement(TouchableHighlight, { testID: `rerenderListButton`, onPress: () => {
setCounter(counter + 1);
} },
React.createElement(Text, null, "Rerender"))));
});
const TestComponent = ({ testID, renderItems, targetPrimaryKey, }) => {
const list = useObject(List, targetPrimaryKey);
const realm = useRealm();
listRenderCounter();
const renderItem = useCallback(({ item }) => React.createElement(Item, { item: item }), []);
const keyExtractor = useCallback((item) => `${item.id}`, []);
const [counter, setCounter] = useState(0);
// This useEffect is to test that the list object reference is not changing when
// the component is re-rendered.
useEffect(() => {
objectChangeCounter();
}, [list]);
if (list === null) {
return React.createElement(View, { testID: testID });
}
listRenderCounter();
const listIdString = list.id.toHexString();
return (React.createElement(View, { testID: testID },
React.createElement(FlatList, { testID: "list", data: list?.items ?? [], keyExtractor: keyExtractor, renderItem: renderItem }),
";",
React.createElement(View, { testID: `list${list.id}` },
React.createElement(View, { testID: `listTitle${list.id}` },
React.createElement(View, { testID: "list" }, renderItems && React.createElement(ItemList, { list: list.items })),
React.createElement(View, { testID: `list${listIdString}` },
React.createElement(View, { testID: `listTitle${listIdString}` },
React.createElement(Text, null, list.title)),
React.createElement(TextInput, { testID: `listTitleInput${list.id}`, value: list.title, onChangeText: (text) => {
React.createElement(TextInput, { testID: `listTitleInput${listIdString}`, value: list.title, onChangeText: (text) => {
realm.write(() => {

@@ -152,3 +181,3 @@ list.title = text;

} }),
React.createElement(TouchableHighlight, { testID: `deleteListButton${list.id}`, onPress: () => {
React.createElement(TouchableHighlight, { testID: `deleteListButton${listIdString}`, onPress: () => {
realm.write(() => {

@@ -161,14 +190,24 @@ realm.delete(list);

React.createElement(Text, null, list?.favoriteItem.name))),
React.createElement(Text, null, list?.tags[0])));
React.createElement(Text, null, list?.tags[0]),
React.createElement(TouchableHighlight, { testID: `rerenderObjectButton`, onPress: () => {
setCounter(counter + 1);
} },
React.createElement(Text, null, "Rerender"))));
};
async function setupTest() {
const { getByTestId, getByText, debug } = render(React.createElement(App, null));
const { getByTestId, getByText, debug, rerender } = render(React.createElement(App, null));
await waitFor(() => getByTestId("testContainer"));
const object = testRealm.objectForPrimaryKey(List, 1);
// In order to test that `useObject` brings the non-existing object into view when it's created,
// we do the creation after the app is rendered.
testRealm.write(() => {
testRealm.create(List, { id: parentObjectId, title: "List", items: testCollection });
});
const object = testRealm.objectForPrimaryKey(List, parentObjectId);
if (!object)
throw new Error("Object not found in Realm");
await waitFor(() => getByTestId("list"));
const collection = object.items;
expect(listRenderCounter).toHaveBeenCalledTimes(1);
expect(itemRenderCounter).toHaveBeenCalledTimes(10);
return { getByTestId, getByText, debug, object, collection };
return { getByTestId, getByText, debug, object, collection, rerender };
}

@@ -179,2 +218,4 @@ describe("useObject: rendering objects with a Realm.List property", () => {

itemRenderCounter.mockClear();
objectChangeCounter.mockClear();
listChangeCounter.mockClear();
});

@@ -185,4 +226,9 @@ afterAll(() => {

describe("rendering single object", () => {
it("render an object in one render cycle", async () => {
it("renders an object in one render cycle", async () => {
const { getByTestId } = render(React.createElement(App, null));
// In order to test that `useObject` brings the non-existing object into view when it's created,
// we do the creation after the app is rendered.
testRealm.write(() => {
testRealm.create(List, { id: parentObjectId, title: "List", items: testCollection });
});
await waitFor(() => getByTestId("list"));

@@ -193,4 +239,5 @@ expect(listRenderCounter).toHaveBeenCalledTimes(1);

const { getByTestId, getByText, object } = await setupTest();
const titleElement = getByTestId(`listTitle${object.id}`);
const inputComponent = getByTestId(`listTitleInput${object.id}`);
const idString = object.id.toHexString();
const titleElement = getByTestId(`listTitle${idString}`);
const inputComponent = getByTestId(`listTitleInput${idString}`);
expect(titleElement).toHaveTextContent("List");

@@ -205,3 +252,4 @@ expect(listRenderCounter).toHaveBeenCalledTimes(1);

const { getByTestId, object } = await setupTest();
const deleteButton = getByTestId(`deleteListButton${object.id}`);
const idString = object.id.toHexString();
const deleteButton = getByTestId(`deleteListButton${idString}`);
fireEvent.press(deleteButton);

@@ -212,10 +260,11 @@ await act(async () => {

expect(object.isValid()).toBe(false);
expect(testRealm.objectForPrimaryKey(List, 1)).toBe(null);
expect(testRealm.objectForPrimaryKey(List, parentObjectId)).toBe(null);
const testContainer = getByTestId("testContainer");
expect(testContainer).toBeEmptyElement();
expect(listRenderCounter).toHaveBeenCalledTimes(2);
// List is now gone, so it wasn't detected as a render
expect(listRenderCounter).toHaveBeenCalledTimes(1);
});
it("test changes to linked object", async () => {
const { getByTestId } = await setupTest();
const object = testRealm.objectForPrimaryKey(List, 1);
const object = testRealm.objectForPrimaryKey(List, parentObjectId);
if (!object)

@@ -247,2 +296,25 @@ throw new Error("Object not found in Realm");

});
it("renders a different list if the target primary key changes", async () => {
const { rerender, getByTestId } = await setupTest();
const newParentObjectId = new Realm.BSON.ObjectId();
testRealm.write(() => {
testRealm.create(List, { id: newParentObjectId, title: "Other List", items: [] });
});
rerender(React.createElement(App, { targetPrimaryKey: newParentObjectId }));
const titleElement = getByTestId(`listTitle${newParentObjectId.toHexString()}`);
expect(titleElement).toHaveTextContent("Other List");
});
it("will return the same reference when state changes", async () => {
const { getByTestId } = await setupTest();
// Force a rerender
const rerenderButton = getByTestId("rerenderObjectButton");
// We expect the object to be re-rendered 3 times, once for the initial render, once for the
// object itself coming into existence, and once adding list of items to the list
const expectedCount = 3;
// Update the state twice to ensure the object reference is the same and we don't have a false positive
fireEvent.press(rerenderButton);
expect(objectChangeCounter).toHaveBeenCalledTimes(expectedCount);
fireEvent.press(rerenderButton);
expect(objectChangeCounter).toHaveBeenCalledTimes(expectedCount);
});
});

@@ -252,2 +324,5 @@ describe("rendering objects with a Realm.List property", () => {

const { getByTestId } = render(React.createElement(App, null));
testRealm.write(() => {
testRealm.create(List, { id: parentObjectId, title: "List", items: testCollection });
});
await waitFor(() => getByTestId("list"));

@@ -259,5 +334,6 @@ expect(itemRenderCounter).toHaveBeenCalledTimes(10);

const id = collection[0].id;
const nameElement = getByTestId(`name${id}`);
const input = getByTestId(`input${id}`);
expect(nameElement).toHaveTextContent(`${id}`);
const idString = id.toHexString();
const nameElement = getByTestId(`name${idString}`);
const input = getByTestId(`input${idString}`);
expect(nameElement).toHaveTextContent(idString);
fireEvent.changeText(input, "apple");

@@ -274,9 +350,11 @@ await waitFor(() => getByText("apple"));

const id = firstItem.id;
const idString = id.toHexString();
const nextVisible = collection[10];
const deleteButton = getByTestId(`deleteButton${id}`);
const nameElement = getByTestId(`name${id}`);
expect(nameElement).toHaveTextContent(`${id}`);
const deleteButton = getByTestId(`deleteButton${idString}`);
const nameElement = getByTestId(`name${idString}`);
expect(nameElement).toHaveTextContent(idString);
expect(itemRenderCounter).toHaveBeenCalledTimes(10);
fireEvent.press(deleteButton);
await waitFor(() => getByTestId(`name${nextVisible.id}`));
const nextIdString = nextVisible.id.toHexString();
await waitFor(() => getByTestId(`name${nextIdString}`));
expect(itemRenderCounter).toHaveBeenCalledTimes(20);

@@ -300,3 +378,3 @@ });

testRealm.write(() => {
collection.unshift(testRealm.create(ListItem, { id: 9999, name: "apple" }));
collection.unshift(testRealm.create(ListItem, { id: new Realm.BSON.ObjectId(), name: "apple" }));
});

@@ -333,4 +411,39 @@ // Force Realm listeners to fire rather than waiting for the text "apple"

});
it("re-renders the list even if the list items have not been rendered", async () => {
const { getByTestId } = render(React.createElement(App, { renderItems: false }));
const list = testRealm.write(() => {
return testRealm.create(List, { id: parentObjectId, title: "List" });
});
await waitFor(() => getByTestId("list"));
expect(listRenderCounter).toHaveBeenCalledTimes(1);
testRealm.write(() => {
list.items.push(testRealm.create(ListItem, testCollection[0]));
});
await act(async () => {
forceSynchronousNotifications(testRealm);
});
expect(listRenderCounter).toHaveBeenCalledTimes(2);
testRealm.write(() => {
list.items.push(testRealm.create(ListItem, testCollection[1]));
});
await act(async () => {
forceSynchronousNotifications(testRealm);
});
expect(listRenderCounter).toHaveBeenCalledTimes(3);
});
});
it("will return the same reference when state changes", async () => {
const { getByTestId } = await setupTest();
// Force a rerender
const rerenderButton = getByTestId("rerenderListButton");
// We expect the object to be re-rendered 2 times, once for the initial render
// and once for adding list of items to the list
const expectedCount = 2;
// Update the state twice to ensure the object reference is the same and we don't have a false positive
fireEvent.press(rerenderButton);
expect(objectChangeCounter).toHaveBeenCalledTimes(expectedCount);
fireEvent.press(rerenderButton);
expect(objectChangeCounter).toHaveBeenCalledTimes(expectedCount);
});
});
//# sourceMappingURL=useObjectRender.test.js.map

@@ -65,2 +65,3 @@ ////////////////////////////////////////////////////////////////////////////

const tagRenderCounter = jest.fn();
const queryObjectChangeCounter = jest.fn();
let testRealm = new Realm(configuration);

@@ -103,7 +104,7 @@ const { useQuery, useObject, RealmProvider, useRealm } = createRealmContext(configuration);

});
const renderTag = ({ item }) => React.createElement(TagComponent, { tag: item });
const tagKeyExtractor = (item) => `tag-${item.id}`;
const ItemComponent = React.memo(({ item }) => {
itemRenderCounter();
const realm = useRealm();
const renderItem = useCallback(({ item }) => React.createElement(TagComponent, { tag: item }), []);
const keyExtractor = useCallback((item) => `tag-${item.id}`, []);
return (React.createElement(View, { testID: `result${item.id}` },

@@ -123,3 +124,3 @@ React.createElement(View, { testID: `name${item.name}` },

React.createElement(Text, null, "Delete")),
React.createElement(FlatList, { horizontal: true, testID: `tagList-${item.id}`, data: item.tags, keyExtractor: keyExtractor, renderItem: renderItem })));
React.createElement(FlatList, { horizontal: true, testID: `tagList-${item.id}`, data: item.tags, keyExtractor: tagKeyExtractor, renderItem: renderTag })));
});

@@ -134,4 +135,6 @@ const TagComponent = React.memo(({ tag }) => {

const SORTED_ARGS = ["id", true];
const keyExtractor = (item) => `${item.id}`;
const TestComponent = ({ queryType, useUseObject }) => {
const collection = useQuery(Item);
const [counter, setCounter] = useState(0);
const result = useMemo(() => {

@@ -147,5 +150,15 @@ switch (queryType) {

}, [queryType, collection]);
// This useEffect is to test that the list object reference is not changing when
// the component is re-rendered.
useEffect(() => {
queryObjectChangeCounter();
}, [result]);
const renderItem = useCallback(({ item }) => (useUseObject ? React.createElement(UseObjectItemComponent, { item: item }) : React.createElement(ItemComponent, { item: item })), [useUseObject]);
const keyExtractor = useCallback((item) => `${item.id}`, []);
return React.createElement(FlatList, { testID: "list", data: result, keyExtractor: keyExtractor, renderItem: renderItem });
return (React.createElement(React.Fragment, null,
React.createElement(FlatList, { testID: "list", data: result, keyExtractor: keyExtractor, renderItem: renderItem }),
";",
React.createElement(TouchableHighlight, { testID: `rerenderButton`, onPress: () => {
setCounter(counter + 1);
} },
React.createElement(Text, null, "Rerender"))));
};

@@ -181,2 +194,3 @@ function getTestCollection(queryType) {

tagRenderCounter.mockClear();
queryObjectChangeCounter.mockClear();
Realm.clearTestState();

@@ -306,3 +320,13 @@ });

});
it("will return the same reference when state changes", async () => {
const { getByTestId } = await setupTest({ queryType });
// Force a rerender
const rerenderButton = getByTestId("rerenderButton");
// Update the state twice to ensure the object reference is the same and we don't have a false positive
fireEvent.press(rerenderButton);
expect(queryObjectChangeCounter).toHaveBeenCalledTimes(1);
fireEvent.press(rerenderButton);
expect(queryObjectChangeCounter).toHaveBeenCalledTimes(1);
});
});
//# sourceMappingURL=useQueryRender.test.js.map

@@ -19,3 +19,3 @@ ////////////////////////////////////////////////////////////////////////////

import { isEqual } from "lodash";
import React, { createContext, useContext, useEffect, useRef, useState } from "react";
import React, { createContext, useContext, useLayoutEffect, useRef, useState } from "react";
import Realm from "realm";

@@ -32,21 +32,8 @@ /**

const configuration = useRef(appProps);
const [app, setApp] = useState(new Realm.App(configuration.current));
// We increment `configVersion` when a config override passed as a prop
// changes, which triggers a `useEffect` to overwrite the current App with the
// new config
const [configVersion, setConfigVersion] = useState(0);
useEffect(() => {
if (!isEqual(appProps, configuration.current)) {
configuration.current = appProps;
setConfigVersion((x) => x + 1);
}
}, [appProps]);
const [app, setApp] = useState(() => new Realm.App(configuration.current));
// Support for a possible change in configuration
useEffect(() => {
if (!isEqual(appProps, configuration.current)) {
configuration.current = appProps;
try {
const app = new Realm.App(configuration.current);
setApp(app);
if (appRef) {
appRef.current = app;
}
setApp(new Realm.App(configuration.current));
}

@@ -56,3 +43,8 @@ catch (err) {

}
}, [configVersion, setApp]);
}
useLayoutEffect(() => {
if (appRef) {
appRef.current = app;
}
}, [appRef, app]);
return React.createElement(AppContext.Provider, { value: app }, children);

@@ -59,0 +51,0 @@ };

@@ -0,1 +1,2 @@

/// <reference types="react" />
import Realm from "realm";

@@ -19,2 +20,8 @@ /**

/**
* Reference boolean which is set to true whenever an object in the collection changes
* It is used to determine if the collection's object reference should be updated
* The implementing component should reset this to false when updating its object reference
*/
updatedRef: React.MutableRefObject<boolean>;
/**
* Optional Map to be used as the cache. This is used to allow a `sorted` or `filtered`

@@ -44,3 +51,3 @@ * (derived) version of the collection to reuse the same cache, preventing excess new object

*/
export declare function createCachedCollection<T extends Realm.Object>({ collection, realm, updateCallback, objectCache, isDerived, }: CachedCollectionArgs<T>): {
export declare function createCachedCollection<T extends Realm.Object>({ collection, realm, updateCallback, updatedRef, objectCache, isDerived, }: CachedCollectionArgs<T>): {
collection: Realm.Collection<T>;

@@ -47,0 +54,0 @@ tearDown: () => void;

@@ -17,3 +17,3 @@ const numericRegEx = /^-?\d+$/;

*/
export function createCachedCollection({ collection, realm, updateCallback, objectCache = new Map(), isDerived = false, }) {
export function createCachedCollection({ collection, realm, updateCallback, updatedRef, objectCache = new Map(), isDerived = false, }) {
const cachedCollectionHandler = {

@@ -31,2 +31,3 @@ get: function (target, key) {

updateCallback,
updatedRef,
objectCache,

@@ -92,2 +93,3 @@ isDerived: true,

});
updatedRef.current = true;
updateCallback();

@@ -94,0 +96,0 @@ }

@@ -0,1 +1,2 @@

/// <reference types="react" />
import Realm from "realm";

@@ -19,3 +20,13 @@ /**

updateCallback: () => void;
/**
* Reference boolean which is set to true whenever the object changes
* It is used to determine if the object's reference should be updated
* The implementing component should reset this to false when updating its object reference
*/
updatedRef: React.MutableRefObject<boolean>;
};
export declare type CachedObject = {
object: Realm.Object | null;
tearDown: () => void;
};
/**

@@ -33,7 +44,4 @@ * Creates a proxy around a {@link Realm.Object} that will return a new reference

*/
export declare function createCachedObject({ object, realm, updateCallback, }: CachedObjectArgs): {
object: Realm.Object | null;
tearDown: () => void;
};
export declare function createCachedObject({ object, realm, updateCallback, updatedRef }: CachedObjectArgs): CachedObject;
export {};
//# sourceMappingURL=cachedObject.d.ts.map

@@ -32,3 +32,3 @@ ////////////////////////////////////////////////////////////////////////////

*/
export function createCachedObject({ object, realm, updateCallback, }) {
export function createCachedObject({ object, realm, updateCallback, updatedRef }) {
const listCaches = new Map();

@@ -40,2 +40,13 @@ const listTearDowns = [];

}
// Create a cache for any Realm.List properties on the object
for (const key of object.keys()) {
//@ts-expect-error - TS doesn't know that the key is a valid property
const value = object[key];
if (value instanceof Realm.List && value.type === "object") {
const updatedRef = { current: true };
const { collection, tearDown } = createCachedCollection({ collection: value, realm, updateCallback, updatedRef });
listCaches.set(key, { collection, updatedRef });
listTearDowns.push(tearDown);
}
}
// This Proxy handler intercepts any accesses into properties of the cached object

@@ -57,10 +68,11 @@ // of type `Realm.List`, and returns a `cachedCollection` wrapping those properties

// only the modified children of the list component actually re-render.
return new Proxy(listCaches.get(key), {});
const { collection, updatedRef } = listCaches.get(key);
if (updatedRef.current) {
updatedRef.current = false;
const proxyCollection = new Proxy(collection, {});
listCaches.set(key, { collection: proxyCollection, updatedRef });
return proxyCollection;
}
return collection;
}
const { collection, tearDown } = createCachedCollection({ collection: value, realm, updateCallback });
// Add to a list of teardowns which will be invoked when the cachedObject's teardown is called
listTearDowns.push(tearDown);
// Store the proxied list into a map to persist the cachedCollection
listCaches.set(key, collection);
return collection;
}

@@ -86,2 +98,3 @@ return value;

}
updatedRef.current = true;
};

@@ -103,3 +116,5 @@ // We cannot add a listener to an invalid object

object.removeListener(listenerCallback);
listTearDowns.forEach((listTearDown) => listTearDown());
for (const listTearDown of listTearDowns) {
listTearDown();
}
};

@@ -106,0 +121,0 @@ return { object: cachedObjectResult, tearDown };

@@ -56,3 +56,5 @@ ////////////////////////////////////////////////////////////////////////////

return ({ children, fallback: Fallback, realmRef, ...restProps }) => {
const [realm, setRealm] = useState(null);
const [realm, setRealm] = useState(() => realmConfig.sync === undefined && restProps.sync === undefined
? new Realm(mergeRealmConfiguration(realmConfig, restProps))
: null);
// Automatically set the user in the configuration if its been set.

@@ -59,0 +61,0 @@ const user = useUser();

@@ -1,2 +0,20 @@

import { useEffect, useReducer, useMemo } from "react";
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2021 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
import Realm from "realm";
import { useEffect, useReducer, useMemo, useRef } from "react";
import { createCachedObject } from "./cachedObject";

@@ -29,2 +47,25 @@ /**

const [, forceRerender] = useReducer((x) => x + 1, 0);
// Get the original object from the realm, so we can check if it exists
const originalObject = realm.objectForPrimaryKey(type, primaryKey);
// Store the primaryKey as a ref, since when it is an objectId or UUID, it will be a new instance on every render
const primaryKeyRef = useRef(primaryKey);
const collectionRef = useRef(realm.objects(type));
const objectRef = useRef();
const updatedRef = useRef(true);
// Initializing references with a function call or class constructor will
// cause the function or constructor to be called on ever render.
// Even though this value is thrown away on subsequent renders, `createCachedObject` will end up registering a listener.
// Therefore, we initialize the references with null, and only create the object if it is null
// Ref: https://github.com/facebook/react/issues/14490
const cachedObjectRef = useRef(null);
if (cachedObjectRef.current === null) {
cachedObjectRef.current = createCachedObject({
object: originalObject ?? null,
realm,
updateCallback: forceRerender,
updatedRef,
});
}
// Create a ref, since the object returned from `objectForPrimaryKey` is always going to have a different reference
const originalObjectRef = useRef(originalObject);
// Wrap the cachedObject in useMemo, so we only replace it with a new instance if `primaryKey` or `type` change

@@ -34,7 +75,20 @@ const { object, tearDown } = useMemo(

// When this is implemented, remove `?? null`
() => createCachedObject({
object: realm.objectForPrimaryKey(type, primaryKey) ?? null,
realm,
updateCallback: forceRerender,
}), [type, realm, primaryKey]);
() => {
// This should never happen, but if it does, we want to return a null result
if (!cachedObjectRef.current) {
return { object: null, tearDown: () => undefined };
}
// Re-instantiate the cachedObject if the primaryKey has changed or the originalObject has gone from null to not null
if (!arePrimaryKeysIdentical(primaryKey, primaryKeyRef.current) ||
(originalObjectRef.current === null && originalObject !== null)) {
cachedObjectRef.current = createCachedObject({
object: originalObject ?? null,
realm,
updateCallback: forceRerender,
updatedRef,
});
originalObjectRef.current = originalObject;
}
return cachedObjectRef.current;
}, [realm, originalObject, primaryKey]);
// Invoke the tearDown of the cachedObject when useObject is unmounted

@@ -44,2 +98,30 @@ useEffect(() => {

}, [tearDown]);
// If the object doesn't exist, listen for insertions to the collection and force a rerender if the inserted object has the correct primary key
useEffect(() => {
const collection = collectionRef.current;
const collectionListener = (_, changes) => {
const primaryKeyProperty = collection?.[0]?.objectSchema()?.primaryKey;
for (const index of changes.insertions) {
const object = collection[index];
if (primaryKeyProperty) {
//@ts-expect-error - if the primaryKeyProperty exists, then it is indexable. However, we don't allow it when we don't actually know the type of the object
const insertedPrimaryKey = object[primaryKeyProperty];
if (arePrimaryKeysIdentical(insertedPrimaryKey, primaryKeyRef.current)) {
forceRerender();
collection.removeListener(collectionListener);
break;
}
}
}
};
if (!originalObjectRef.current) {
collection.addListener(collectionListener);
}
return () => {
// If the app is closing, the realm will be closed and the listener does not need to be removed if
if (!realm.isClosed && collection) {
collection.removeListener(collectionListener);
}
};
}, [realm, type, forceRerender]);
// If the object has been deleted or doesn't exist for the given primary key, just return null

@@ -49,6 +131,27 @@ if (object === null || object?.isValid() === false) {

}
// Wrap object in a proxy to update the reference on rerender ( should only rerender when something has changed )
return new Proxy(object, {});
if (updatedRef.current) {
// Wrap object in a proxy to update the reference on rerender ( should only rerender when something has changed )
objectRef.current = new Proxy(object, {});
updatedRef.current = false;
}
// This will never be undefined, but the type system doesn't know that
return objectRef.current;
};
}
// This is a helper function that determines if two primary keys are equal. It will also handle the case where the primary key is an ObjectId or UUID
function arePrimaryKeysIdentical(a, b) {
if (typeof a !== typeof b) {
return false;
}
if (typeof a === "string" || typeof a === "number") {
return a === b;
}
if (a instanceof Realm.BSON.ObjectId && b instanceof Realm.BSON.ObjectId) {
return a.toHexString() === b.toHexString();
}
if (a instanceof Realm.BSON.UUID && b instanceof Realm.BSON.UUID) {
return a.toHexString() === b.toHexString();
}
return false;
}
//# sourceMappingURL=useObject.js.map

@@ -18,3 +18,3 @@ ////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
import { useEffect, useReducer, useMemo } from "react";
import { useEffect, useReducer, useMemo, useRef } from "react";
import { createCachedCollection } from "./cachedCollection";

@@ -54,4 +54,13 @@ import { symbols } from "@realm/common";

const [, forceRerender] = useReducer((x) => x + 1, 0);
const collectionRef = useRef();
const updatedRef = useRef(true);
// Wrap the cachedObject in useMemo, so we only replace it with a new instance if `primaryKey` or `type` change
const { collection, tearDown } = useMemo(() => createCachedCollection({ collection: realm.objects(type), realm, updateCallback: forceRerender }), [type, realm]);
const { collection, tearDown } = useMemo(() => {
return createCachedCollection({
collection: realm.objects(type),
realm,
updateCallback: forceRerender,
updatedRef,
});
}, [type, realm]);
// Invoke the tearDown of the cachedCollection when useQuery is unmounted

@@ -63,16 +72,20 @@ useEffect(() => {

// Also we are ensuring the type returned is Realm.Results, as this is known in this context
const proxy = new Proxy(collection, {});
// Store the original, unproxied result as a non-enumerable field with a symbol
// key on the proxy object, so that we can check for this and get the original results
// when passing the result of `useQuery` into the subscription mutation methods
// (see `lib/mutable-subscription-set.js` for more details)
Object.defineProperty(proxy, symbols.PROXY_TARGET, {
value: realm.objects(type),
enumerable: false,
configurable: false,
writable: true,
});
return proxy;
if (updatedRef.current) {
updatedRef.current = false;
collectionRef.current = new Proxy(collection, {});
// Store the original, unproxied result as a non-enumerable field with a symbol
// key on the proxy object, so that we can check for this and get the original results
// when passing the result of `useQuery` into the subscription mutation methods
// (see `lib/mutable-subscription-set.js` for more details)
Object.defineProperty(collectionRef.current, symbols.PROXY_TARGET, {
value: realm.objects(type),
enumerable: false,
configurable: false,
writable: true,
});
}
// This will never not be defined, but the type system doesn't know that
return collectionRef.current;
};
}
//# sourceMappingURL=useQuery.js.map

@@ -30,11 +30,8 @@ ////////////////////////////////////////////////////////////////////////////

const app = useApp();
const [user, setUser] = useState(null);
const [user, setUser] = useState(() => app.currentUser);
// Support for a possible change in configuration
if (app.currentUser?.id != user?.id) {
setUser(app.currentUser);
}
useEffect(() => {
if (!app.currentUser || user?.id != app.currentUser.id) {
setUser(app.currentUser);
}
// Ignoring updates to user, as this would cause a potential infinite loop
}, [app, setUser]);
useEffect(() => {
const event = () => {

@@ -41,0 +38,0 @@ if (app.currentUser?.id != user?.id) {

{
"name": "@realm/react",
"version": "0.4.1",
"version": "0.4.3",
"description": "React specific hooks and implementation helpers for Realm",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc