Skip to content

Commit

Permalink
Improve AppProvider and UserProvider rendering perf (#5215)
Browse files Browse the repository at this point in the history
* Improve AppProvider and UserProvider rendering perf

These providers would vend stale state into their context after props changing, which would require another unnecessary full re-render to get updated context values via `useEffect` hooks. Instead, now state is updated during render so as to immediately restart rendering before stale state is rendered down the context, which is more efficient.

For more information on this approach, please see the [Adjusting some state when a props changes](https://beta.reactjs.org/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes) section of React beta docs on the "You Might Not Need an Effect" page.

* Update appRef with useLayoutEffect in AppProvider

Refs are typically updated by React before running layout effects, so this better matches those semantics.
  • Loading branch information
appden committed Jan 13, 2023
1 parent ad6684b commit d60dfa1
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 28 deletions.
34 changes: 13 additions & 21 deletions packages/realm-react/src/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//
////////////////////////////////////////////////////////////////////////////
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";

/**
Expand All @@ -41,32 +41,24 @@ type AppProviderProps = Realm.AppConfiguration & {
export const AppProvider: React.FC<AppProviderProps> = ({ children, appRef, ...appProps }) => {
const configuration = useRef<Realm.AppConfiguration>(appProps);

const [app, setApp] = useState<Realm.App>(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<Realm.App>(() => 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));
} catch (err) {
console.error(err);
}
}, [configVersion, setApp]);
}

useLayoutEffect(() => {
if (appRef) {
appRef.current = app;
}
}, [appRef, app]);

return <AppContext.Provider value={app}>{children}</AppContext.Provider>;
};
Expand Down
11 changes: 4 additions & 7 deletions packages/realm-react/src/UserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,12 @@ type UserProviderProps = {
*/
export const UserProvider: React.FC<UserProviderProps> = ({ fallback: Fallback, children }) => {
const app = useApp();
const [user, setUser] = useState<Realm.User | null>(null);
const [user, setUser] = useState<Realm.User | null>(() => app.currentUser);

// Support for a possible change in configuration
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]);
if (app.currentUser?.id != user?.id) {
setUser(app.currentUser);
}

useEffect(() => {
const event = () => {
Expand Down

0 comments on commit d60dfa1

Please sign in to comment.