diff --git a/packages/realm-react/src/RealmContext.ts b/packages/realm-react/src/RealmContext.ts new file mode 100644 index 0000000000..a74c326ae7 --- /dev/null +++ b/packages/realm-react/src/RealmContext.ts @@ -0,0 +1,71 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 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 { createContext } from "react"; +import Realm from "realm"; + +import { RealmProvider, createRealmProvider } from "./RealmProvider"; +import { UseObjectHook, createUseObject } from "./useObject"; +import { UseQueryHook, createUseQuery } from "./useQuery"; +import { UseRealmHook, createUseRealm } from "./useRealm"; + +export type RealmContext = { + RealmProvider: RealmProvider; + useRealm: UseRealmHook; + useQuery: UseQueryHook; + useObject: UseObjectHook; +}; + +/** + * Creates Realm React hooks and Provider component for a given Realm configuration + * @example + * ``` + *class Task extends Realm.Object { + * ... + * + * static schema = { + * name: 'Task', + * primaryKey: '_id', + * properties: { + * ... + * }, + * }; + *} + * + *const {useRealm, useQuery, useObject, RealmProvider} = createRealmContext({schema: [Task]}); + * ``` + * @param realmConfig - {@link Realm.Configuration} used to open the Realm + * @returns An object containing a `RealmProvider` component, and `useRealm`, `useQuery` and `useObject` hooks + */ +export const createRealmContext: (realmConfig?: Realm.Configuration) => RealmContext = ( + realmConfig: Realm.Configuration = {}, +) => { + const RealmContext = createContext(null); + const RealmProvider = createRealmProvider(realmConfig, RealmContext); + + const useRealm = createUseRealm(RealmContext); + const useQuery = createUseQuery(useRealm); + const useObject = createUseObject(useRealm); + + return { + RealmProvider, + useRealm, + useQuery, + useObject, + }; +}; diff --git a/packages/realm-react/src/RealmProvider.tsx b/packages/realm-react/src/RealmProvider.tsx index 43cb788424..a4355569aa 100644 --- a/packages/realm-react/src/RealmProvider.tsx +++ b/packages/realm-react/src/RealmProvider.tsx @@ -26,7 +26,7 @@ type PartialRealmConfiguration = Omit, "sync"> & { sync?: Partial; }; -type ProviderProps = PartialRealmConfiguration & { +export type ProviderProps = PartialRealmConfiguration & { /** * The fallback component to render if the Realm is not opened. */ @@ -44,6 +44,33 @@ type ProviderProps = PartialRealmConfiguration & { children: React.ReactNode; }; +/** + * The Provider component that is required to wrap any component using + * the Realm hooks. + * @example + * ``` + * const AppRoot = () => { + * const syncConfig = { + * flexible: true, + * user: currentUser + * }; + * + * return ( + * + * + * + * ) + * } + * ``` + * @param props - The {@link Realm.Configuration} for this Realm defaults to + * the config passed to `createRealmProvider`, but individual config keys can + * be overridden when creating a `` by passing them as props. + * For example, to override the `path` config value, use a prop named `path` + * e.g., `path="newPath.realm"` + * an attribute of the same key. + */ +export type RealmProvider = React.FC; + /** * Generates a `RealmProvider` given a {@link Realm.Configuration} and {@link React.Context}. * @param realmConfig - The configuration of the Realm to be instantiated @@ -53,7 +80,7 @@ type ProviderProps = PartialRealmConfiguration & { export function createRealmProvider( realmConfig: Realm.Configuration, RealmContext: React.Context, -): React.FC { +): RealmProvider { /** * Returns a Context Provider component that is required to wrap any component using * the Realm hooks. diff --git a/packages/realm-react/src/UserProvider.tsx b/packages/realm-react/src/UserProvider.tsx index 90da85ee20..4b57b68dc9 100644 --- a/packages/realm-react/src/UserProvider.tsx +++ b/packages/realm-react/src/UserProvider.tsx @@ -75,7 +75,6 @@ export const UserProvider: React.FC = ({ fallback: Fallback, * {@link UserProvider} context. The user is stored as React state, * so will trigger a re-render whenever it changes (e.g. logging in, * logging out, switching user). - * */ export const useUser = < FunctionsFactoryType extends Realm.DefaultFunctionsFactory, diff --git a/packages/realm-react/src/index.tsx b/packages/realm-react/src/index.tsx index d6f36b118c..6f3abd46f4 100644 --- a/packages/realm-react/src/index.tsx +++ b/packages/realm-react/src/index.tsx @@ -17,142 +17,12 @@ //////////////////////////////////////////////////////////////////////////// import Realm from "realm"; -import React, { createContext } from "react"; - -import { createRealmProvider } from "./RealmProvider"; -import { createUseObject } from "./useObject"; -import { createUseQuery } from "./useQuery"; -import { createUseRealm } from "./useRealm"; +import React from "react"; +import { createRealmContext } from "./RealmContext"; export type { UseObjectHook } from "./useObject"; export type { UseQueryHook, QueryHookOptions, QueryHookClassBasedOptions } from "./useQuery"; -type RealmContext = { - /** - * The Provider component that is required to wrap any component using - * the Realm hooks. - * @example - * ``` - * const AppRoot = () => { - * const syncConfig = { - * flexible: true, - * user: currentUser - * }; - * - * return ( - * - * - * - * ) - * } - * ``` - * @param props - The {@link Realm.Configuration} for this Realm defaults to - * the config passed to `createRealmProvider`, but individual config keys can - * be overridden when creating a `` by passing them as props. - * For example, to override the `path` config value, use a prop named `path` - * e.g., `path="newPath.realm"` - * an attribute of the same key. - */ - RealmProvider: ReturnType; - /** - * Returns the instance of the {@link Realm} opened by the `RealmProvider`. - * @example - * ``` - * const realm = useRealm(); - * ``` - * @returns a realm instance - */ - useRealm: ReturnType; - - /** - * Returns a {@link Realm.Collection} of {@link Realm.Object}s from a given type. - * The hook will update on any changes to any object in the collection - * and return an empty array if the collection is empty. - * - * The result of this can be consumed directly by the `data` argument of any React Native - * VirtualizedList or FlatList. If the component used for the list's `renderItem` prop is {@link React.Memo}ized, - * then only the modified object will re-render. - * @example - * ```tsx - * // Return all collection items - * const collection = useQuery({ type: Object }); - * - * // Return all collection items sorted by name and filtered by category - * const filteredAndSorted = useQuery({ - * type: Object, - * query: (collection) => collection.filtered('category == $0',category).sorted('name'), - * }, [category]); - * - * // Return all collection items sorted by name and filtered by category, triggering re-renders only if "name" changes - * const filteredAndSorted = useQuery({ - * type: Object, - * query: (collection) => collection.filtered('category == $0',category).sorted('name'), - * keyPaths: ["name"] - * }, [category]); - * ``` - * @param options - * @param options.type - The object type, depicted by a string or a class extending Realm.Object - * @param options.query - A function that takes a {@link Realm.Collection} and returns a {@link Realm.Collection} of the same type. This allows for filtering and sorting of the collection, before it is returned. - * @param options.keyPaths - Indicates a lower bound on the changes relevant for the hook. This is a lower bound, since if multiple hooks add listeners (each with their own `keyPaths`) the union of these key-paths will determine the changes that are considered relevant for all listeners registered on the collection. In other words: A listener might fire and cause a re-render more than the key-paths specify, if other listeners with different key-paths are present. - * @param deps - An array of dependencies that will be passed to {@link React.useMemo} - * @returns a collection of realm objects or an empty array - */ - useQuery: ReturnType; - /** - * Returns a {@link Realm.Object} from a given type and value of primary key. - * The hook will update on any changes to the properties on the returned object - * and return null if it either doesn't exists or has been deleted. - * @example - * ``` - * const object = useObject(ObjectClass, objectId); - * ``` - * @param type - The object type, depicted by a string or a class extending {@link Realm.Object} - * @param primaryKey - The primary key of the desired object which will be retrieved using {@link Realm.objectForPrimaryKey} - * @param keyPaths - Indicates a lower bound on the changes relevant for the hook. This is a lower bound, since if multiple hooks add listeners (each with their own `keyPaths`) the union of these key-paths will determine the changes that are considered relevant for all listeners registered on the object. In other words: A listener might fire and cause a re-render more than the key-paths specify, if other listeners with different key-paths are present. - * @returns either the desired {@link Realm.Object} or `null` in the case of it being deleted or not existing. - */ - useObject: ReturnType; -}; - -/** - * Creates Realm React hooks and Provider component for a given Realm configuration - * @example - * ``` - *class Task extends Realm.Object { - * ... - * - * static schema = { - * name: 'Task', - * primaryKey: '_id', - * properties: { - * ... - * }, - * }; - *} - * - *const {useRealm, useQuery, useObject, RealmProvider} = createRealmContext({schema: [Task]}); - * ``` - * @param realmConfig - {@link Realm.Configuration} used to open the Realm - * @returns An object containing a `RealmProvider` component, and `useRealm`, `useQuery` and `useObject` hooks - */ -export const createRealmContext: (realmConfig?: Realm.Configuration) => RealmContext = ( - realmConfig: Realm.Configuration = {}, -) => { - const RealmContext = createContext(null); - const RealmProvider = createRealmProvider(realmConfig, RealmContext); - - const useRealm = createUseRealm(RealmContext); - const useQuery = createUseQuery(useRealm); - const useObject = createUseObject(useRealm); - - return { - RealmProvider, - useRealm, - useQuery, - useObject, - }; -}; - const defaultContext = createRealmContext(); /** diff --git a/packages/realm-react/src/useAuth.tsx b/packages/realm-react/src/useAuth.tsx index e400fcd167..517cf15d80 100644 --- a/packages/realm-react/src/useAuth.tsx +++ b/packages/realm-react/src/useAuth.tsx @@ -23,7 +23,9 @@ import { AuthOperationName, AuthResult } from "./types"; import { useApp, useAuthResult } from "./AppProvider"; import { useAuthOperation } from "./useAuthOperation"; -interface UseAuth { +export type UseAuthHook = () => AuthMethods; + +interface AuthMethods { /** * Log in with a {@link Realm.Credentials} instance. This allows login with any * authentication mechanism supported by Realm. @@ -108,7 +110,7 @@ interface UseAuth { * code. * @returns An object containing operations and state for authenticating with an Atlas App. */ -export function useAuth(): UseAuth { +export const useAuth: UseAuthHook = () => { const app = useApp(); const [result] = useAuthResult(); @@ -182,4 +184,4 @@ export function useAuth(): UseAuth { logInWithFunction, logOut, }; -} +}; diff --git a/packages/realm-react/src/useAuthOperation.tsx b/packages/realm-react/src/useAuthOperation.tsx index 0370f85b01..f55c6f3f8c 100644 --- a/packages/realm-react/src/useAuthOperation.tsx +++ b/packages/realm-react/src/useAuthOperation.tsx @@ -21,13 +21,12 @@ import { useCallback } from "react"; import { AuthError, AuthOperationName, OperationState } from "./types"; import { useAuthResult } from "./AppProvider"; -export function useAuthOperation({ - operation, - operationName, -}: { +type UseAuthOperationOptions = { operation: (...args: Args) => Promise; operationName: AuthOperationName; -}) { +}; + +export function useAuthOperation({ operation, operationName }: UseAuthOperationOptions) { const [result, setResult] = useAuthResult(); return useCallback<(...args: Args) => void>( diff --git a/packages/realm-react/src/useEmailPasswordAuth.tsx b/packages/realm-react/src/useEmailPasswordAuth.tsx index f952502c97..74a531e153 100644 --- a/packages/realm-react/src/useEmailPasswordAuth.tsx +++ b/packages/realm-react/src/useEmailPasswordAuth.tsx @@ -23,7 +23,7 @@ import { AuthOperationName, AuthResult } from "./types"; import { useApp, useAuthResult } from "./AppProvider"; import { useAuthOperation } from "./useAuthOperation"; -interface UseEmailPasswordAuth { +interface EmailPasswordAuthMethods { /** * Convenience function to log in a user with an email and password - users * could also call `logIn(Realm.Credentials.emailPassword(email, password))`. @@ -107,7 +107,7 @@ interface UseEmailPasswordAuth { * code. * @returns An object containing operations and state related to Email/Password authentication. */ -export function useEmailPasswordAuth(): UseEmailPasswordAuth { +export function useEmailPasswordAuth(): EmailPasswordAuthMethods { const app = useApp(); const [result] = useAuthResult(); diff --git a/packages/realm-react/src/useObject.tsx b/packages/realm-react/src/useObject.tsx index 0cc323c487..d59a95a648 100644 --- a/packages/realm-react/src/useObject.tsx +++ b/packages/realm-react/src/useObject.tsx @@ -44,6 +44,19 @@ export type ObjectHookClassBasedOptions = { keyPaths?: string | string[]; }; +/** + * Returns a {@link Realm.Object} from a given type and value of primary key. + * The hook will update on any changes to the properties on the returned object + * and return null if it either doesn't exists or has been deleted. + * @example + * ``` + * const object = useObject(ObjectClass, objectId); + * ``` + * @param type - The object type, depicted by a string or a class extending {@link Realm.Object} + * @param primaryKey - The primary key of the desired object which will be retrieved using {@link Realm.objectForPrimaryKey} + * @param keyPaths - Indicates a lower bound on the changes relevant for the hook. This is a lower bound, since if multiple hooks add listeners (each with their own `keyPaths`) the union of these key-paths will determine the changes that are considered relevant for all listeners registered on the object. In other words: A listener might fire and cause a re-render more than the key-paths specify, if other listeners with different key-paths are present. + * @returns either the desired {@link Realm.Object} or `null` in the case of it being deleted or not existing. + */ export type UseObjectHook = { (options: ObjectHookOptions): (T & Realm.Object) | null; (options: ObjectHookClassBasedOptions): T | null; diff --git a/packages/realm-react/src/useQuery.tsx b/packages/realm-react/src/useQuery.tsx index cfeb629f9c..7968eb9e3b 100644 --- a/packages/realm-react/src/useQuery.tsx +++ b/packages/realm-react/src/useQuery.tsx @@ -37,6 +37,39 @@ export type QueryHookClassBasedOptions = { keyPaths?: string | string[]; }; +/** + * Returns a {@link Realm.Collection} of {@link Realm.Object}s from a given type. + * The hook will update on any changes to any object in the collection + * and return an empty array if the collection is empty. + * + * The result of this can be consumed directly by the `data` argument of any React Native + * VirtualizedList or FlatList. If the component used for the list's `renderItem` prop is {@link React.Memo}ized, + * then only the modified object will re-render. + * @example + * ```tsx + * // Return all collection items + * const collection = useQuery({ type: Object }); + * + * // Return all collection items sorted by name and filtered by category + * const filteredAndSorted = useQuery({ + * type: Object, + * query: (collection) => collection.filtered('category == $0',category).sorted('name'), + * }, [category]); + * + * // Return all collection items sorted by name and filtered by category, triggering re-renders only if "name" changes + * const filteredAndSorted = useQuery({ + * type: Object, + * query: (collection) => collection.filtered('category == $0',category).sorted('name'), + * keyPaths: ["name"] + * }, [category]); + * ``` + * @param options + * @param options.type - The object type, depicted by a string or a class extending Realm.Object + * @param options.query - A function that takes a {@link Realm.Collection} and returns a {@link Realm.Collection} of the same type. This allows for filtering and sorting of the collection, before it is returned. + * @param options.keyPaths - Indicates a lower bound on the changes relevant for the hook. This is a lower bound, since if multiple hooks add listeners (each with their own `keyPaths`) the union of these key-paths will determine the changes that are considered relevant for all listeners registered on the collection. In other words: A listener might fire and cause a re-render more than the key-paths specify, if other listeners with different key-paths are present. + * @param deps - An array of dependencies that will be passed to {@link React.useMemo} + * @returns a collection of realm objects or an empty array + */ export type UseQueryHook = { (options: QueryHookOptions, deps?: DependencyList): Realm.Results>; (options: QueryHookClassBasedOptions, deps?: DependencyList): Realm.Results; diff --git a/packages/realm-react/src/useRealm.tsx b/packages/realm-react/src/useRealm.tsx index 596c9d60ef..e274ff2463 100644 --- a/packages/realm-react/src/useRealm.tsx +++ b/packages/realm-react/src/useRealm.tsx @@ -19,6 +19,14 @@ import Realm from "realm"; import { useContext } from "react"; +/** + * Returns the instance of the {@link Realm} opened by the `RealmProvider`. + * @example + * ``` + * const realm = useRealm(); + * ``` + * @returns a realm instance + */ export type UseRealmHook = { (): Realm; }; diff --git a/packages/realm/src/deprecated-global.ts b/packages/realm/src/deprecated-global.ts index 83c870d6b4..6c07c41cd2 100644 --- a/packages/realm/src/deprecated-global.ts +++ b/packages/realm/src/deprecated-global.ts @@ -56,7 +56,7 @@ declare global { * @deprecated Will be removed in v13.0.0. Please use an import statement. */ export namespace Realm { - export import Realm = RealmItself; + export import Realm = RealmConstructor; export import flags = internal.flags; export import Object = internal.RealmObject;