From 34a3a99d621ee823752107861f206f3f829e07da Mon Sep 17 00:00:00 2001 From: Himself65 Date: Wed, 15 Feb 2023 02:51:44 -0600 Subject: [PATCH] refactor: use suspense in AppDefender (#1020) --- apps/web/src/pages/_app.tsx | 17 ++++--- .../providers/app-state-provider/Provider.tsx | 23 ++++------ .../providers/app-state-provider/interface.ts | 1 - apps/web/src/store/app/index.tsx | 44 +++++++++---------- .../data-center/src/provider/local/utils.ts | 5 ++- packages/data-center/src/utils/index.ts | 1 + 6 files changed, 44 insertions(+), 47 deletions(-) diff --git a/apps/web/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx index cadf6ca02d..77f360a433 100644 --- a/apps/web/src/pages/_app.tsx +++ b/apps/web/src/pages/_app.tsx @@ -16,14 +16,13 @@ import { ModalProvider } from '@/store/globalModal'; // import AppStateProvider2 from '@/providers/app-state-provider2/provider'; import { useRouter } from 'next/router'; -import { useEffect } from 'react'; -import { useAppState } from '@/providers/app-state-provider'; +import { Suspense, useEffect } from 'react'; import { PageLoading } from '@/components/loading'; import Head from 'next/head'; import '@affine/i18n'; import { useTranslation } from '@affine/i18n'; import React from 'react'; -import { GlobalAppProvider } from '@/store/app'; +import { DataCenterLoader, GlobalAppProvider } from '@/store/app'; const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), { ssr: false, @@ -80,7 +79,13 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => { {NoNeedAppStatePageList.includes(router.route) ? ( getLayout() ) : ( - {getLayout()} + }> + {/* we should put this before every component in case of they read a null value */} + + + {getLayout()} + + )} @@ -90,15 +95,13 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => { const AppDefender = ({ children }: PropsWithChildren) => { const router = useRouter(); - const { synced } = useAppState(); - useEffect(() => { if (['/index.html', '/'].includes(router.asPath)) { router.replace('/workspace'); } }, [router]); - return
{synced ? children : }
; + return <>{children}; }; export default App; diff --git a/apps/web/src/providers/app-state-provider/Provider.tsx b/apps/web/src/providers/app-state-provider/Provider.tsx index 285dfd8d3d..80b42e4d30 100644 --- a/apps/web/src/providers/app-state-provider/Provider.tsx +++ b/apps/web/src/providers/app-state-provider/Provider.tsx @@ -45,21 +45,14 @@ export const AppStateProvider = ({ const onceRef = useRef(true); const dataCenter = useGlobalState(store => store.dataCenter); - useEffect(() => { - if (dataCenter !== null) { - if (onceRef.current) { - setAppState({ - workspaceList: dataCenter.workspaces, - currentWorkspace: null, - pageList: [], - synced: true, - }); - onceRef.current = false; - } else { - console.warn('dataCenter Effect called twice. Please fix this ASAP'); - } - } - }, [dataCenter]); + if (onceRef.current && dataCenter) { + setAppState({ + workspaceList: dataCenter.workspaces, + currentWorkspace: null, + pageList: [], + }); + onceRef.current = false; + } useEffect(() => { // FIXME: onWorkspacesChange should have dispose function diff --git a/apps/web/src/providers/app-state-provider/interface.ts b/apps/web/src/providers/app-state-provider/interface.ts index e1a81ca041..1cd54018f2 100644 --- a/apps/web/src/providers/app-state-provider/interface.ts +++ b/apps/web/src/providers/app-state-provider/interface.ts @@ -18,7 +18,6 @@ export type AppStateValue = { workspaceList: WorkspaceUnit[]; currentWorkspace: WorkspaceUnit | null; pageList: PageMeta[]; - synced: boolean; blobDataSynced?: boolean; }; diff --git a/apps/web/src/store/app/index.tsx b/apps/web/src/store/app/index.tsx index 60bb9abbda..8ea54ed60f 100644 --- a/apps/web/src/store/app/index.tsx +++ b/apps/web/src/store/app/index.tsx @@ -1,5 +1,5 @@ import type React from 'react'; -import { createContext, useContext, useEffect, useMemo, useRef } from 'react'; +import { createContext, useContext, useMemo } from 'react'; import { createStore, StateCreator, useStore } from 'zustand'; import { combine, subscribeWithSelector } from 'zustand/middleware'; import type { UseBoundStore } from 'zustand/react'; @@ -27,6 +27,7 @@ export type GlobalActionsCreator = StateCreator< export interface GlobalState extends BlockSuiteState, UserState { readonly dataCenter: DataCenter; + readonly dataCenterPromise: Promise; } export interface GlobalActions extends BlockSuiteActions, UserActions {} @@ -40,6 +41,8 @@ const create = () => ...createUserState(), // eslint-disable-next-line @typescript-eslint/no-non-null-assertion dataCenter: null!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + dataCenterPromise: null!, }, /* deepscan-disable TOO_MANY_ARGS */ (set, get, api) => ({ @@ -71,27 +74,25 @@ export const useGlobalState: UseBoundStore = (( // eslint-disable-next-line @typescript-eslint/no-explicit-any }) as any; -function DataCenterSideEffect() { - const onceRef = useRef(true); +export function DataCenterLoader() { + const dataCenter = useGlobalState(store => store.dataCenter); + const dataCenterPromise = useGlobalState(store => store.dataCenterPromise); const api = useGlobalStateApi(); - useEffect(() => { - async function init() { - const dataCenterPromise = getDataCenter(); - dataCenterPromise.then(async dataCenter => { - // Ensure datacenter has at least one workspace - if (dataCenter.workspaces.length === 0) { - await createDefaultWorkspace(dataCenter); - } - api.setState({ dataCenter }); - }); - } - if (onceRef.current) { - onceRef.current = false; - init().then(() => { - console.log('datacenter init success'); - }); - } - }, [api]); + if (!dataCenter && !dataCenterPromise) { + const promise = getDataCenter(); + api.setState({ dataCenterPromise: promise }); + promise.then(async dataCenter => { + // Ensure datacenter has at least one workspace + if (dataCenter.workspaces.length === 0) { + await createDefaultWorkspace(dataCenter); + } + api.setState({ dataCenter }); + }); + throw promise; + } + if (!dataCenter) { + throw dataCenterPromise; + } return null; } @@ -99,7 +100,6 @@ export const GlobalAppProvider: React.FC = function ModelProvider({ children }) { return ( create(), [])}> - {children} ); diff --git a/packages/data-center/src/provider/local/utils.ts b/packages/data-center/src/provider/local/utils.ts index 3788014105..2bf7fd06f4 100644 --- a/packages/data-center/src/provider/local/utils.ts +++ b/packages/data-center/src/provider/local/utils.ts @@ -29,8 +29,9 @@ export const createWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => { await setDefaultAvatar(blocksuiteWorkspace); workspaceUnit.update({ avatar: blocksuiteWorkspace.meta.avatar }); } - - await writeUpdatesToLocal(blocksuiteWorkspace); + if (typeof window !== 'undefined') { + await writeUpdatesToLocal(blocksuiteWorkspace); + } workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace); diff --git a/packages/data-center/src/utils/index.ts b/packages/data-center/src/utils/index.ts index 5c93abd900..7e401d68a3 100644 --- a/packages/data-center/src/utils/index.ts +++ b/packages/data-center/src/utils/index.ts @@ -11,6 +11,7 @@ export const createBlocksuiteWorkspace = ( return new BlocksuiteWorkspace({ room: workspaceId, defaultFlags: { enable_slash_menu: true }, + isSSR: typeof window === 'undefined', ...workspaceOption, }) .register(builtInSchemas)