From 9f2c9ee76e7872698a92bc477926e511a101103f Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 5 Apr 2024 20:33:02 +0200 Subject: [PATCH] Remove repetitive query of ClientConfig and CurrentWorkspace member (#4859) ## Removing repetitive queries (impacting performance on page load) We have recently introduced the capability to detect schema version mismatch. To do that, we add a new header in all our queries. On page load, this header is added once we know the currentUser and especially its currentWorkspace and related schema version. However, applying this header to apollo client will re-trigger all queries that have been already performed (GetClientConfig and GetCurrentUser). To avoid re-triggering them, I'm introducing two new "isLoaded" states and skip the query if the query has already been performed ## Fixing Relation Detail not displaying data on show page Small bug introduced in a previous PR --- packages/twenty-front/src/index.tsx | 4 + .../auth/states/isCurrentUserLoadingState.ts | 6 ++ .../components/ClientConfigProvider.tsx | 61 +--------------- .../components/ClientConfigProviderEffect.tsx | 71 ++++++++++++++++++ .../states/isClientConfigLoadedState.ts | 6 ++ .../hooks/useLazyFindOneRecord.ts | 8 +- .../modules/users/components/UserProvider.tsx | 67 ++--------------- .../users/components/UserProviderEffect.tsx | 73 +++++++++++++++++++ 8 files changed, 175 insertions(+), 121 deletions(-) create mode 100644 packages/twenty-front/src/modules/auth/states/isCurrentUserLoadingState.ts create mode 100644 packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx create mode 100644 packages/twenty-front/src/modules/client-config/states/isClientConfigLoadedState.ts create mode 100644 packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx diff --git a/packages/twenty-front/src/index.tsx b/packages/twenty-front/src/index.tsx index ef0192fd4..756d1cc59 100644 --- a/packages/twenty-front/src/index.tsx +++ b/packages/twenty-front/src/index.tsx @@ -6,6 +6,7 @@ import { RecoilRoot } from 'recoil'; import { ApolloProvider } from '@/apollo/components/ApolloProvider'; import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider'; +import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect'; import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect'; import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; @@ -22,6 +23,7 @@ import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/Sn import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider'; import { ThemeType } from '@/ui/theme/constants/ThemeLight'; import { UserProvider } from '@/users/components/UserProvider'; +import { UserProviderEffect } from '@/users/components/UserProviderEffect'; import { PageChangeEffect } from '~/effect-components/PageChangeEffect'; import '@emotion/react'; @@ -46,7 +48,9 @@ root.render( + + diff --git a/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadingState.ts b/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadingState.ts new file mode 100644 index 000000000..0a62d92ab --- /dev/null +++ b/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadingState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const isCurrentUserLoadedState = createState({ + key: 'isCurrentUserLoadedState', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx index db62ad64f..671842687 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx @@ -1,64 +1,11 @@ -import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; -import { authProvidersState } from '@/client-config/states/authProvidersState'; -import { billingState } from '@/client-config/states/billingState'; -import { isDebugModeState } from '@/client-config/states/isDebugModeState'; -import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState'; -import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState'; -import { sentryConfigState } from '@/client-config/states/sentryConfigState'; -import { supportChatState } from '@/client-config/states/supportChatState'; -import { telemetryState } from '@/client-config/states/telemetryState'; -import { useGetClientConfigQuery } from '~/generated/graphql'; -import { isDefined } from '~/utils/isDefined'; +import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState'; export const ClientConfigProvider: React.FC = ({ children, }) => { - const setAuthProviders = useSetRecoilState(authProvidersState); - const setIsDebugMode = useSetRecoilState(isDebugModeState); + const isClientConfigLoaded = useRecoilValue(isClientConfigLoadedState); - const setIsSignInPrefilled = useSetRecoilState(isSignInPrefilledState); - const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState); - - const setBilling = useSetRecoilState(billingState); - const setTelemetry = useSetRecoilState(telemetryState); - const setSupportChat = useSetRecoilState(supportChatState); - - const setSentryConfig = useSetRecoilState(sentryConfigState); - - const { data, loading } = useGetClientConfigQuery(); - - useEffect(() => { - if (isDefined(data?.clientConfig)) { - setAuthProviders({ - google: data?.clientConfig.authProviders.google, - password: data?.clientConfig.authProviders.password, - magicLink: false, - }); - setIsDebugMode(data?.clientConfig.debugMode); - setIsSignInPrefilled(data?.clientConfig.signInPrefilled); - setIsSignUpDisabled(data?.clientConfig.signUpDisabled); - - setBilling(data?.clientConfig.billing); - setTelemetry(data?.clientConfig.telemetry); - setSupportChat(data?.clientConfig.support); - - setSentryConfig({ - dsn: data?.clientConfig?.sentry?.dsn, - }); - } - }, [ - data, - setAuthProviders, - setIsDebugMode, - setIsSignInPrefilled, - setIsSignUpDisabled, - setTelemetry, - setSupportChat, - setBilling, - setSentryConfig, - ]); - - return loading ? <> : <>{children}; + return isClientConfigLoaded ? <>{children} : <>; }; diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx new file mode 100644 index 000000000..e8d57ee71 --- /dev/null +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx @@ -0,0 +1,71 @@ +import { useEffect } from 'react'; +import { useRecoilState, useSetRecoilState } from 'recoil'; + +import { authProvidersState } from '@/client-config/states/authProvidersState'; +import { billingState } from '@/client-config/states/billingState'; +import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState'; +import { isDebugModeState } from '@/client-config/states/isDebugModeState'; +import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState'; +import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState'; +import { sentryConfigState } from '@/client-config/states/sentryConfigState'; +import { supportChatState } from '@/client-config/states/supportChatState'; +import { telemetryState } from '@/client-config/states/telemetryState'; +import { useGetClientConfigQuery } from '~/generated/graphql'; +import { isDefined } from '~/utils/isDefined'; + +export const ClientConfigProviderEffect = () => { + const setAuthProviders = useSetRecoilState(authProvidersState); + const setIsDebugMode = useSetRecoilState(isDebugModeState); + + const setIsSignInPrefilled = useSetRecoilState(isSignInPrefilledState); + const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState); + + const setBilling = useSetRecoilState(billingState); + const setTelemetry = useSetRecoilState(telemetryState); + const setSupportChat = useSetRecoilState(supportChatState); + + const setSentryConfig = useSetRecoilState(sentryConfigState); + const [isClientConfigLoaded, setIsClientConfigLoaded] = useRecoilState( + isClientConfigLoadedState, + ); + + const { data, loading } = useGetClientConfigQuery({ + skip: isClientConfigLoaded, + }); + + useEffect(() => { + if (!loading && isDefined(data?.clientConfig)) { + setIsClientConfigLoaded(true); + setAuthProviders({ + google: data?.clientConfig.authProviders.google, + password: data?.clientConfig.authProviders.password, + magicLink: false, + }); + setIsDebugMode(data?.clientConfig.debugMode); + setIsSignInPrefilled(data?.clientConfig.signInPrefilled); + setIsSignUpDisabled(data?.clientConfig.signUpDisabled); + + setBilling(data?.clientConfig.billing); + setTelemetry(data?.clientConfig.telemetry); + setSupportChat(data?.clientConfig.support); + + setSentryConfig({ + dsn: data?.clientConfig?.sentry?.dsn, + }); + } + }, [ + data, + setAuthProviders, + setIsDebugMode, + setIsSignInPrefilled, + setIsSignUpDisabled, + setTelemetry, + setSupportChat, + setBilling, + setSentryConfig, + loading, + setIsClientConfigLoaded, + ]); + + return <>; +}; diff --git a/packages/twenty-front/src/modules/client-config/states/isClientConfigLoadedState.ts b/packages/twenty-front/src/modules/client-config/states/isClientConfigLoadedState.ts new file mode 100644 index 000000000..7b6cff0b3 --- /dev/null +++ b/packages/twenty-front/src/modules/client-config/states/isClientConfigLoadedState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const isClientConfigLoadedState = createState({ + key: 'isClientConfigLoadedState', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useLazyFindOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useLazyFindOneRecord.ts index e905b58a2..c8a372526 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useLazyFindOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useLazyFindOneRecord.ts @@ -32,8 +32,12 @@ export const useLazyFindOneRecord = ({ findOneRecord: ({ objectRecordId, onCompleted }: FindOneRecordParams) => findOneRecord({ variables: { objectRecordId }, - onCompleted: (data) => - onCompleted?.(getRecordFromRecordNode(data[objectNameSingular])), + onCompleted: (data) => { + const record = getRecordFromRecordNode({ + recordNode: data[objectNameSingular], + }); + onCompleted?.(record); + }, }), called, error, diff --git a/packages/twenty-front/src/modules/users/components/UserProvider.tsx b/packages/twenty-front/src/modules/users/components/UserProvider.tsx index e1cc61fdf..5287cb4e2 100644 --- a/packages/twenty-front/src/modules/users/components/UserProvider.tsx +++ b/packages/twenty-front/src/modules/users/components/UserProvider.tsx @@ -1,67 +1,10 @@ -import React, { useEffect, useState } from 'react'; -import { useQuery } from '@apollo/client'; -import { useSetRecoilState } from 'recoil'; +import React from 'react'; +import { useRecoilValue } from 'recoil'; -import { currentUserState } from '@/auth/states/currentUserState'; -import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { workspacesState } from '@/auth/states/workspaces'; -import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; -import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; -import { User } from '~/generated/graphql'; -import { isDefined } from '~/utils/isDefined'; +import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState'; export const UserProvider = ({ children }: React.PropsWithChildren) => { - const [isLoading, setIsLoading] = useState(true); + const isCurrentUserLoaded = useRecoilValue(isCurrentUserLoadedState); - const setCurrentUser = useSetRecoilState(currentUserState); - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); - const setWorkspaces = useSetRecoilState(workspacesState); - - const setCurrentWorkspaceMember = useSetRecoilState( - currentWorkspaceMemberState, - ); - - const { loading: queryLoading, data: queryData } = useQuery<{ - currentUser: User; - }>(GET_CURRENT_USER); - - useEffect(() => { - if (!queryLoading) { - setIsLoading(false); - } - - if (!isDefined(queryData?.currentUser)) return; - - setCurrentUser(queryData.currentUser); - setCurrentWorkspace(queryData.currentUser.defaultWorkspace); - - const { workspaceMember, workspaces: userWorkspaces } = - queryData.currentUser; - - if (isDefined(workspaceMember)) { - setCurrentWorkspaceMember({ - ...workspaceMember, - colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light', - }); - } - - if (isDefined(userWorkspaces)) { - const workspaces = userWorkspaces - .map(({ workspace }) => workspace) - .filter(isDefined); - - setWorkspaces(workspaces); - } - }, [ - setCurrentUser, - isLoading, - queryLoading, - setCurrentWorkspace, - setCurrentWorkspaceMember, - setWorkspaces, - queryData?.currentUser, - ]); - - return isLoading ? <> : <>{children}; + return !isCurrentUserLoaded ? <> : <>{children}; }; diff --git a/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx b/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx new file mode 100644 index 000000000..f4f10c417 --- /dev/null +++ b/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from 'react'; +import { useQuery } from '@apollo/client'; +import { useRecoilState, useSetRecoilState } from 'recoil'; + +import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState'; +import { workspacesState } from '@/auth/states/workspaces'; +import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; +import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; +import { User } from '~/generated/graphql'; +import { isDefined } from '~/utils/isDefined'; + +export const UserProviderEffect = () => { + const [isLoading, setIsLoading] = useState(true); + + const [isCurrentUserLoaded, setIsCurrentUserLoaded] = useRecoilState( + isCurrentUserLoadedState, + ); + const setCurrentUser = useSetRecoilState(currentUserState); + const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const setWorkspaces = useSetRecoilState(workspacesState); + + const setCurrentWorkspaceMember = useSetRecoilState( + currentWorkspaceMemberState, + ); + + const { loading: queryLoading, data: queryData } = useQuery<{ + currentUser: User; + }>(GET_CURRENT_USER, { skip: isCurrentUserLoaded }); + + useEffect(() => { + if (!queryLoading) { + setIsLoading(false); + } + + if (!isDefined(queryData?.currentUser)) return; + + setIsCurrentUserLoaded(true); + setCurrentUser(queryData.currentUser); + setCurrentWorkspace(queryData.currentUser.defaultWorkspace); + + const { workspaceMember, workspaces: userWorkspaces } = + queryData.currentUser; + + if (isDefined(workspaceMember)) { + setCurrentWorkspaceMember({ + ...workspaceMember, + colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light', + }); + } + + if (isDefined(userWorkspaces)) { + const workspaces = userWorkspaces + .map(({ workspace }) => workspace) + .filter(isDefined); + + setWorkspaces(workspaces); + } + }, [ + setCurrentUser, + isLoading, + queryLoading, + setCurrentWorkspace, + setCurrentWorkspaceMember, + setWorkspaces, + queryData?.currentUser, + setIsCurrentUserLoaded, + ]); + + return <>; +};