Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kraenhansen committed Mar 6, 2024
1 parent 8cbdcd6 commit 9bd0c91
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 192 deletions.
2 changes: 1 addition & 1 deletion packages/realm-react/src/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const AuthOperationProvider: React.FC<AuthOperationProps> = ({ children }) => {
* can be used to create a Realm.App instance:
* https://www.mongodb.com/docs/realm-sdks/js/latest/Realm.App.html#~AppConfiguration
*/
type AppProviderProps = Realm.AppConfiguration & {
export type AppProviderProps = Realm.AppConfiguration & {
/**
* A ref to the App instance. This is useful if you need to access the App
* instance outside of a component that uses the App hooks.
Expand Down
71 changes: 71 additions & 0 deletions packages/realm-react/src/RealmContext.ts
Original file line number Diff line number Diff line change
@@ -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<Realm | null>(null);
const RealmProvider = createRealmProvider(realmConfig, RealmContext);

const useRealm = createUseRealm(RealmContext);
const useQuery = createUseQuery(useRealm);
const useObject = createUseObject(useRealm);

return {
RealmProvider,
useRealm,
useQuery,
useObject,
};
};
33 changes: 30 additions & 3 deletions packages/realm-react/src/RealmProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import { isEqual } from "lodash";

import { UserContext } from "./UserProvider";

type PartialRealmConfiguration = Omit<Partial<Realm.Configuration>, "sync"> & {
export type PartialRealmConfiguration = Omit<Partial<Realm.Configuration>, "sync"> & {
sync?: Partial<Realm.SyncConfiguration>;
};

type ProviderProps = PartialRealmConfiguration & {
export type RealmProviderProps = PartialRealmConfiguration & {
/**
* The fallback component to render if the Realm is not opened.
*/
Expand All @@ -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 (
* <RealmProvider schema={[Task, User]} path={"data.realm"} sync={syncConfig}>
* <App/>
* </RealmProvider>
* )
* }
* ```
* @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 `<RealmProvider>` 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<RealmProviderProps>;

/**
* Generates a `RealmProvider` given a {@link Realm.Configuration} and {@link React.Context}.
* @param realmConfig - The configuration of the Realm to be instantiated
Expand All @@ -53,7 +80,7 @@ type ProviderProps = PartialRealmConfiguration & {
export function createRealmProvider(
realmConfig: Realm.Configuration,
RealmContext: React.Context<Realm | null>,
): React.FC<ProviderProps> {
): RealmProvider {
/**
* Returns a Context Provider component that is required to wrap any component using
* the Realm hooks.
Expand Down
3 changes: 1 addition & 2 deletions packages/realm-react/src/UserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { useApp } from "./AppProvider";
*/
export const UserContext = createContext<Realm.User | null>(null);

type UserProviderProps = {
export type UserProviderProps = {
/**
* The fallback component to render if there is no authorized user. This can be used
* to render a login screen or another component which will log the user in.
Expand Down Expand Up @@ -75,7 +75,6 @@ export const UserProvider: React.FC<UserProviderProps> = ({ 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,
Expand Down
5 changes: 3 additions & 2 deletions packages/realm-react/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ export type CollectionCallback = Parameters<typeof Realm.Results.prototype.addLi
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export type AnyRealmObject = Realm.Object<any>;

export type RealmClassType<T = any> = { new (...args: any): T };
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export type RealmClassType<T extends AnyRealmObject = AnyRealmObject> = { new (...args: any): T };

export function isClassModelConstructor(value: unknown): value is RealmClassType<unknown> {
export function isClassModelConstructor(value: unknown): value is RealmClassType {
return Object.getPrototypeOf(value) === Realm.Object;
}
175 changes: 11 additions & 164 deletions packages/realm-react/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,141 +17,21 @@
////////////////////////////////////////////////////////////////////////////

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";

export type { RealmProviderProps, PartialRealmConfiguration } from "./RealmProvider";
export type { UserProviderProps } from "./UserProvider";
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 (
* <RealmProvider schema={[Task, User]} path={"data.realm"} sync={syncConfig}>
* <App/>
* </RealmProvider>
* )
* }
* ```
* @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 `<RealmProvider>` 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<typeof createRealmProvider>;
/**
* Returns the instance of the {@link Realm} opened by the `RealmProvider`.
* @example
* ```
* const realm = useRealm();
* ```
* @returns a realm instance
*/
useRealm: ReturnType<typeof createUseRealm>;

/**
* 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<typeof createUseQuery>;
/**
* 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<typeof createUseObject>;
};

/**
* 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<Realm | null>(null);
const RealmProvider = createRealmProvider(realmConfig, RealmContext);
export type {
UseQueryHook,
QueryHookOptions,
QueryHookClassBasedOptions,
QueryCallback,
DependencyList,
} from "./useQuery";

const useRealm = createUseRealm(RealmContext);
const useQuery = createUseQuery(useRealm);
const useObject = createUseObject(useRealm);
import { UseQueryHook } from "./useQuery";

return {
RealmProvider,
useRealm,
useQuery,
useObject,
};
};
import { createRealmContext } from "./RealmContext";

const defaultContext = createRealmContext();

Expand Down Expand Up @@ -188,39 +68,6 @@ export const RealmProvider = defaultContext.RealmProvider;
*/
export const useRealm = defaultContext.useRealm;

/**
* 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 const useQuery = defaultContext.useQuery;

/**
Expand Down
8 changes: 5 additions & 3 deletions packages/realm-react/src/useAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import { AuthOperationName, AuthResult } from "./types";
import { useApp, useAuthResult } from "./AppProvider";
import { useAuthOperation } from "./useAuthOperation";

interface UseAuth {
export type UseAuthHook = () => AuthMethods;

export interface AuthMethods {
/**
* Log in with a {@link Realm.Credentials} instance. This allows login with any
* authentication mechanism supported by Realm.
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -182,4 +184,4 @@ export function useAuth(): UseAuth {
logInWithFunction,
logOut,
};
}
};
Loading

0 comments on commit 9bd0c91

Please sign in to comment.