diff --git a/packages/realm-react/src/__tests__/useQueryHook.test.tsx b/packages/realm-react/src/__tests__/useQueryHook.test.tsx index 8c23d1cf81..5a6cc9dadf 100644 --- a/packages/realm-react/src/__tests__/useQueryHook.test.tsx +++ b/packages/realm-react/src/__tests__/useQueryHook.test.tsx @@ -102,6 +102,15 @@ describe("useQuery", () => { expect(result.current.length).toBe(3); }); + describe("passing function as 1st argument, deps as 2nd and options as 3rd", () => { + it("can filter objects via a 'query' property", () => { + const { result, renders } = profileHook(() => + useQuery((dogs) => dogs.filtered("age > 10"), [], { type: "dog" }), + ); + expect(result.current.length).toBe(3); + expect(renders).toHaveLength(1); + }); + }); describe("passing an object of options as argument", () => { it("can filter objects via a 'query' property", () => { const { result, renders } = profileHook(() => diff --git a/packages/realm-react/src/index.tsx b/packages/realm-react/src/index.tsx index 2c6e04cd27..20c0b59e24 100644 --- a/packages/realm-react/src/index.tsx +++ b/packages/realm-react/src/index.tsx @@ -130,3 +130,5 @@ export { useUser, UserProvider } from "./UserProvider"; export * from "./useAuth"; export * from "./useEmailPasswordAuth"; export * from "./types"; +export { createUseObject } from "./useObject"; +export { createUseQuery } from "./useQuery"; diff --git a/packages/realm-react/src/useQuery.tsx b/packages/realm-react/src/useQuery.tsx index cfeb629f9c..0e5ccb80f4 100644 --- a/packages/realm-react/src/useQuery.tsx +++ b/packages/realm-react/src/useQuery.tsx @@ -25,23 +25,28 @@ import { AnyRealmObject, RealmClassType, getObjects, isClassModelConstructor } f type QueryCallback = (collection: Realm.Results) => Realm.Results; type DependencyList = ReadonlyArray; -export type QueryHookOptions = { - type: string; - query?: QueryCallback; +export type QueryHookPartialOptions = { + type: string | RealmClassType; keyPaths?: string | string[]; }; -export type QueryHookClassBasedOptions = { - type: RealmClassType; +export type QueryHookOptions = QueryHookPartialOptions & { query?: QueryCallback; - keyPaths?: string | string[]; }; export type UseQueryHook = { (options: QueryHookOptions, deps?: DependencyList): Realm.Results>; - (options: QueryHookClassBasedOptions, deps?: DependencyList): Realm.Results; + (options: QueryHookOptions, deps?: DependencyList): Realm.Results; (type: string): Realm.Results>; (type: RealmClassType): Realm.Results; + ( + query: QueryCallback, + deps: DependencyList, + options: QueryHookPartialOptions, + ): Realm.Results; + (query: QueryCallback, deps: DependencyList, options: QueryHookPartialOptions): Realm.Results< + T & Realm.Object + >; /** @deprecated To help the `react-hooks/exhaustive-deps` eslint rule detect missing dependencies, we've suggest passing a option object as the first argument */ (type: string, query?: QueryCallback, deps?: DependencyList): Realm.Results>; @@ -53,6 +58,12 @@ export type UseQueryHook = { ): Realm.Results; }; +type PossibleQueryArgs = { + typeOrOptionsOrQuery: QueryHookOptions | string | RealmClassType | QueryCallback; + queryOrDeps?: DependencyList | QueryCallback; + depsOrPartialOptions?: DependencyList | QueryHookPartialOptions; +}; + /** * Maps a value to itself */ @@ -67,7 +78,7 @@ function identity(value: T): T { */ export function createUseQuery(useRealm: () => Realm): UseQueryHook { function useQuery( - { type, query = identity, keyPaths }: QueryHookOptions | QueryHookClassBasedOptions, + { type, query = identity, keyPaths }: QueryHookOptions, deps: DependencyList = [], ): Realm.Results { const realm = useRealm(); @@ -132,21 +143,63 @@ export function createUseQuery(useRealm: () => Realm): UseQueryHook { } return function useQueryOverload( - typeOrOptions: QueryHookOptions | QueryHookClassBasedOptions | string | RealmClassType, - queryOrDeps: DependencyList | QueryCallback = identity, - deps: DependencyList = [], + typeOrOptionsOrQuery: PossibleQueryArgs["typeOrOptionsOrQuery"], + queryOrDeps: PossibleQueryArgs["queryOrDeps"] = identity, + depsOrPartialOptions: PossibleQueryArgs["depsOrPartialOptions"] = [], ): Realm.Results { - if (typeof typeOrOptions === "string" && typeof queryOrDeps === "function") { - /* eslint-disable-next-line react-hooks/rules-of-hooks -- We're calling `useQuery` once in any of the brances */ - return useQuery({ type: typeOrOptions, query: queryOrDeps }, deps); - } else if (isClassModelConstructor(typeOrOptions) && typeof queryOrDeps === "function") { - /* eslint-disable-next-line react-hooks/rules-of-hooks -- We're calling `useQuery` once in any of the brances */ - return useQuery({ type: typeOrOptions as RealmClassType, query: queryOrDeps }, deps); - } else if (typeof typeOrOptions === "object" && typeOrOptions !== null) { - /* eslint-disable-next-line react-hooks/rules-of-hooks -- We're calling `useQuery` once in any of the brances */ - return useQuery(typeOrOptions, Array.isArray(queryOrDeps) ? queryOrDeps : deps); - } else { - throw new Error("Unexpected arguments passed to useQuery"); + const args = { typeOrOptionsOrQuery, queryOrDeps, depsOrPartialOptions }; + /* eslint-disable react-hooks/rules-of-hooks -- We're calling `useQuery` once in any of the brances */ + if (isTypeFunctionDeps(args)) { + return useQuery({ type: args.typeOrOptionsOrQuery, query: args.queryOrDeps }, args.depsOrPartialOptions); + } + if (isOptionsDepsNone(args)) { + return useQuery(args.typeOrOptionsOrQuery, Array.isArray(args.queryOrDeps) ? args.queryOrDeps : []); } + if (isFunctionDepsOptions(args)) { + return useQuery({ ...args.depsOrPartialOptions, query: args.typeOrOptionsOrQuery }, args.queryOrDeps); + } + /* eslint-enable react-hooks/rules-of-hooks */ + + throw new Error("Unexpected arguments passed to useQuery"); }; } + +function isTypeFunctionDeps(args: PossibleQueryArgs): args is { + typeOrOptionsOrQuery: string | RealmClassType; + queryOrDeps: QueryCallback; + depsOrPartialOptions: DependencyList; +} { + const { typeOrOptionsOrQuery, queryOrDeps, depsOrPartialOptions } = args; + return ( + (typeof typeOrOptionsOrQuery === "string" || isClassModelConstructor(typeOrOptionsOrQuery)) && + typeof queryOrDeps === "function" && + Array.isArray(depsOrPartialOptions) + ); +} + +function isOptionsDepsNone(args: PossibleQueryArgs): args is { + typeOrOptionsOrQuery: QueryHookOptions; + queryOrDeps: DependencyList | typeof identity; + depsOrPartialOptions: never; +} { + const { typeOrOptionsOrQuery, queryOrDeps } = args; + return ( + typeof typeOrOptionsOrQuery === "object" && + typeOrOptionsOrQuery !== null && + (Array.isArray(queryOrDeps) || queryOrDeps === identity) + ); +} + +function isFunctionDepsOptions(args: PossibleQueryArgs): args is { + typeOrOptionsOrQuery: QueryCallback; + queryOrDeps: DependencyList; + depsOrPartialOptions: QueryHookPartialOptions; +} { + const { typeOrOptionsOrQuery, queryOrDeps, depsOrPartialOptions } = args; + return ( + typeof typeOrOptionsOrQuery === "function" && + Array.isArray(queryOrDeps) && + typeof depsOrPartialOptions === "object" && + depsOrPartialOptions !== null + ); +} diff --git a/packages/realm/package.json b/packages/realm/package.json index 8b6bd6e478..8302554242 100644 --- a/packages/realm/package.json +++ b/packages/realm/package.json @@ -357,4 +357,4 @@ 6 ] } -} +} \ No newline at end of file