Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RJS-2861: Deprecate AppConfiguration.app and add app instance to App Provider #6788

Merged
merged 11 commits into from
Jul 16, 2024
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Deprecations
* The callback for `SyncSession.addProgressNotification` taking `transferred` and `transferable` arguments is deprecated and will be removed. See **Enhancements** below for the new callback supporting both Flexible Sync and Partition-Based Sync. ([#6743](https://github.com/realm/realm-js/pull/6743))
* `AppConfiguration.app` is no longer used by Atlas Device Sync. It will be removed in future SDK releases and should not be used. ([#6785](https://github.com/realm/realm-js/issues/6785))

### Enhancements
* Added progress notifications support for Flexible Sync using an `estimate` as the new callback argument. The `estimate` is roughly equivalent to an estimated value of `transferred / transferable` in the deprecated Partition-Based Sync callback. ([#6743](https://github.com/realm/realm-js/pull/6743))
Expand Down
19 changes: 15 additions & 4 deletions packages/realm-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
## vNext (TBD)

### Deprecations
* None

### Enhancements
* None
* Added the ability to pass an existing `Realm.App` instance in `AppProvider` with the `app` prop. ([#6785](https://github.com/realm/realm-js/issues/6785))
```jsx
import { AppProvider } from "@realm/react";

const app = new Realm.App(...);

function MyApp() {
return (
<AppProvider app={app}>
...
</AppProvider>
);
}
```

### Fixed
* Fixed listener that was not being removed during unmounting of `useObject` and `useQuery` if the listener was added in a write transaction. ([#6552](https://github.com/realm/realm-js/pull/6552)) Thanks [@bimusiek](https://github.com/bimusiek)!
* The `app` prop in `AppProvider` meant for `LocalAppConfiguration` was not being used by Atlas Device Sync and has been removed. `app` is now only used to pass an existing `Realm.App` to the provider. ([#6785](https://github.com/realm/realm-js/pull/6785))

### Compatibility
* React Native >= v0.71.4
Expand Down
96 changes: 81 additions & 15 deletions packages/realm-react/src/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
////////////////////////////////////////////////////////////////////////////

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

import { AuthResult, OperationState } from "./types";
import { RestrictivePick } from "./helpers";

type AppContextValue = Realm.App | null;

Expand Down Expand Up @@ -48,12 +49,8 @@ const AuthOperationProvider: React.FC<AuthOperationProps> = ({ children }) => {
);
};

/**
* Props for the AppProvider component. These replicate the options which
* can be used to create a Realm.App instance:
* https://www.mongodb.com/docs/realm-sdks/js/latest/Realm.App.html#~AppConfiguration
*/
gagik marked this conversation as resolved.
Show resolved Hide resolved
type AppProviderProps = Realm.AppConfiguration & {
/** Mutually exclusive props for the AppProvider component when using a {@link Realm.AppConfiguration} */
type AppProviderWithConfigurationProps = Omit<Realm.AppConfiguration, "app"> & {
/**
* 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 All @@ -62,23 +59,92 @@ type AppProviderProps = Realm.AppConfiguration & {
children: React.ReactNode;
};

/** Props the AppProvider component when using an existing {@link Realm.App} instance */
type AppProviderWithAppProps = {
app: Realm.App;
children: React.ReactNode;
};

/** Combined props for the AppProvider component, used when defining mutually exclusive props */
type AppProviderProps = AppProviderWithAppProps & AppProviderWithConfigurationProps;

/** Mutually exclusive props for the AppProvider component when using an existing {@link Realm.App} instance */
type DynamicAppProviderWithAppProps = RestrictivePick<AppProviderProps, keyof AppProviderWithAppProps>;

/** Mutually exclusive props for the AppProvider component when using a {@link Realm.AppConfiguration} */
type DynamicAppProviderWithConfigurationProps = RestrictivePick<
AppProviderProps,
keyof AppProviderWithConfigurationProps
>;

/**
* Props for the AppProvider component. You can either pass an existing {@link Realm.App} through the `app` prop
* or props that replicate the {@link Realm.AppConfiguration} that is used to create a Realm.App instance.
*/
type DynamicAppProviderProps = DynamicAppProviderWithAppProps | DynamicAppProviderWithConfigurationProps;

/**
* React component providing a Realm App instance on the context for the
* sync hooks to use. An `AppProvider` is required for an app to use the hooks.
* @param props - Either the {@link Realm.AppConfiguration} for App Services passed as props **or** the {@link Realm.App} passed through the `app` prop.
* @param appRef - Provides a ref to the app instance, which can be used to access the app instance outside of the React component tree. **Not available when using the `app` prop**.
*/
export function AppProvider(props: DynamicAppProviderProps): React.ReactNode;
/**
* React component providing a Realm App instance on the context for the
* sync hooks to use. An `AppProvider` is required for an app to use the hooks.
* @param appProps - The {@link Realm.AppConfiguration} for app services, passed as props.
* @param props - The {@link Realm.AppConfiguration} for App Services, passed as props.
* @param appRef - A ref to the app instance, which can be used to access the app instance outside of the React component tree.
*/
export const AppProvider: React.FC<AppProviderProps> = ({ children, appRef, ...appProps }) => {
const configuration = useRef<Realm.AppConfiguration>(appProps);
export function AppProvider(props: DynamicAppProviderWithConfigurationProps): React.ReactNode;
/**
* React component providing a Realm App instance on the context for the
* sync hooks to use. An `AppProvider` is required for an app to use the hooks.
* @param app - The {@link Realm.App} for the provider.
*/
export function AppProvider(props: DynamicAppProviderWithAppProps): React.ReactNode;
export function AppProvider({ children, app, ...config }: DynamicAppProviderProps): React.ReactNode {
if (app instanceof App) {
if (Object.keys(config).length > 0) {
throw new Error("Cannot use configuration props when using an existing App instance.");
}
return <AppProviderWithApp app={app}>{children}</AppProviderWithApp>;
} else if (typeof app !== "undefined") {
throw new Error(
`The "app" prop is used to pass an existing Realm.App instance into an AppProvider. Either remove it or pass a valid Realm.App.`,
);
}

const [app, setApp] = useState<Realm.App>(() => new Realm.App(configuration.current));
return (
<AppProviderWithConfiguration {...(config as AppProviderWithConfigurationProps)}>
{children}
</AppProviderWithConfiguration>
);
}

function AppProviderWithApp({ app, children }: React.PropsWithChildren<AppProviderWithAppProps>) {
return (
<AppContext.Provider value={app}>
<AuthOperationProvider>{children}</AuthOperationProvider>
</AppContext.Provider>
);
}

function AppProviderWithConfiguration({
appRef,
children,
...config
}: React.PropsWithChildren<AppProviderWithConfigurationProps>) {
const configRef = useRef<Realm.AppConfiguration>(config);

const [app, setApp] = useState<Realm.App>(() => new Realm.App(configRef.current));

// Support for a possible change in configuration
if (!isEqual(appProps, configuration.current)) {
configuration.current = appProps;
if (!isEqual(config, configRef.current)) {
configRef.current = config as Realm.AppConfiguration;

try {
setApp(new Realm.App(configuration.current));
setApp(new Realm.App(configRef.current));
} catch (err) {
console.error(err);
}
Expand All @@ -95,7 +161,7 @@ export const AppProvider: React.FC<AppProviderProps> = ({ children, appRef, ...a
<AuthOperationProvider>{children}</AuthOperationProvider>
</AppContext.Provider>
);
};
}

/**
* Hook to access the current {@link Realm.App} from the {@link AppProvider} context.
Expand Down
4 changes: 2 additions & 2 deletions packages/realm-react/src/RealmProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export type DynamicRealmProviderWithRealmProps = RestrictivePick<RealmProviderPr
/**
* Represents properties of a {@link DynamicRealmProvider} where Realm configuration props are set and Realm instance props are disallowed.
*/
export type DynamicsRealmProviderWithConfigurationProps = RestrictivePick<
export type DynamicRealmProviderWithConfigurationProps = RestrictivePick<
RealmProviderProps,
keyof RealmProviderConfigurationProps
>;
Expand All @@ -88,7 +88,7 @@ export type DynamicsRealmProviderWithConfigurationProps = RestrictivePick<
* Supports either {@link RealmProviderRealmProps} or {@link RealmProviderConfigurationProps}.
*/
export type DynamicRealmProvider = React.FC<
DynamicRealmProviderWithRealmProps | DynamicsRealmProviderWithConfigurationProps
DynamicRealmProviderWithRealmProps | DynamicRealmProviderWithConfigurationProps
>;

export function createRealmProviderFromRealm(
Expand Down
Loading
Loading